Groovy Scripting for Automation – 12 Tested Examples for Practical Tasks

Groovy scripting for automation with 12+ examples. Covers CLI parsing, file processing, CSV/JSON scripts, database tasks, and @Grab setup. Tested on Groovy 5.x.

“The best automation script is the one you didn’t have to compile, package, or deploy. Just write it and run it.”

Dave Thomas, The Pragmatic Programmer

Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 25 minutes

Why Groovy for Scripting and Automation?

If you’ve ever written a quick Python script to rename files, parse a log, or automate a database task – and then wished you could use the entire Java ecosystem while doing it – Groovy scripting is exactly what you need. Groovy runs on the JVM, has access to every Java library ever written, and yet feels as lightweight and expressive as any scripting language out there.

Here’s the thing that makes Groovy automation so powerful: you don’t need a main() method, you don’t need a class declaration, you don’t need to compile anything. Just write your logic in a .groovy file and run it with groovy myScript.groovy. That’s it. And with @Grab, you can pull in any Maven dependency at runtime – no build tool, no pom.xml, no build.gradle needed.

In this guide, we’ll walk through 12 practical Groovy scripting examples – from basic scripts with no boilerplate, to command-line argument parsing, file processing, CSV and JSON automation, database scripts, log analysis, backup utilities, and more. Every example is tested on Groovy 5.x and ready to copy-paste into your workflow. This post covers everything from basic scripts to production-ready automation, all without leaving the JVM.

For running system commands from Groovy, also check out our Groovy Process Execution guide. For file I/O fundamentals, see Groovy File Read, Write, Delete. And if your automation involves databases, our Groovy SQL Database Connection tutorial covers that in depth.

Practical Examples

Example 1: Groovy as a Scripting Language (No Main Method)

What we’re doing: Writing a Groovy script without any class or main() method – just top-level code that runs immediately.

Example 1: Basic Groovy Script

// No class, no main method, no imports needed for basics
// Save as: hello_script.groovy
// Run with: groovy hello_script.groovy

def name = "World"
def timestamp = new Date().format('yyyy-MM-dd HH:mm:ss')

println "Hello, ${name}!"
println "Script executed at: ${timestamp}"
println "Groovy version: ${GroovySystem.version}"
println "Java version: ${System.getProperty('java.version')}"
println "OS: ${System.getProperty('os.name')}"

// Variables and functions at script level
def greet(String who) {
    return "Welcome, ${who}! Ready to automate?"
}

println greet("Developer")

// Lists and maps work out of the box
def tools = ['Groovy', 'Gradle', 'Jenkins', 'Spock']
println "Automation tools: ${tools.join(', ')}"
println "Tool count: ${tools.size()}"

Output

Hello, World!
Script executed at: 2026-03-09 10:15:30
Groovy version: 5.0.0
Java version: 17.0.2
OS: Linux
Welcome, Developer! Ready to automate?
Automation tools: Groovy, Gradle, Jenkins, Spock
Tool count: 4

What happened here: The entire file is a script. Groovy wraps it in a class behind the scenes, but you never see that. You get top-level variables, functions, and full access to the JDK – all without a single line of ceremony. This is what makes Groovy scripting so attractive for quick automation tasks.

Example 2: Command-Line Argument Parsing with CliBuilder

What we’re doing: Using Groovy’s built-in CliBuilder to parse command-line arguments like a professional CLI tool.

Example 2: CliBuilder Argument Parsing

// Save as: cli_tool.groovy
// Run with: groovy cli_tool.groovy -n Alice -c 3 --verbose

def cli = new CliBuilder(usage: 'cli_tool.groovy [options]')
cli.n(longOpt: 'name', args: 1, argName: 'name', required: true, 'Your name')
cli.c(longOpt: 'count', args: 1, argName: 'num', 'Number of greetings (default: 1)')
cli.v(longOpt: 'verbose', 'Enable verbose output')
cli.h(longOpt: 'help', 'Show usage information')

def options = cli.parse(args)
if (!options) return  // Parse error already printed

if (options.h) {
    cli.usage()
    return
}

def name = options.n
def count = (options.c ?: '1') as int
def verbose = options.v

if (verbose) {
    println "Running in verbose mode"
    println "Name: ${name}"
    println "Count: ${count}"
    println "-" * 30
}

count.times { i ->
    println "${i + 1}. Hello, ${name}!"
}

println "Done! Generated ${count} greeting(s)."

Output

Running in verbose mode
Name: Alice
Count: 3
------------------------------
1. Hello, Alice!
2. Hello, Alice!
3. Hello, Alice!
Done! Generated 3 greeting(s).

What happened here: CliBuilder is built into Groovy – no external libraries needed. You define options with short and long flags, specify whether arguments are required, and get automatic help message generation. The args variable in a Groovy script automatically holds command-line arguments, just like String[] args in Java’s main().

Example 3: File Automation – Batch Rename and Organize

What we’re doing: Reading, writing, and organizing files – the bread and butter of any Groovy automation script.

Example 3: File Automation Script

// File automation: scan a directory, organize files by extension

def workDir = File.createTempDir('groovy_demo_')
println "Working directory: ${workDir.absolutePath}"

// Create sample files
['report.txt', 'data.csv', 'notes.txt', 'config.json',
 'backup.csv', 'readme.md', 'settings.json'].each { name ->
    new File(workDir, name).text = "Sample content for ${name}"
}

// List all files
println "\n--- All files ---"
workDir.eachFile { file ->
    println "  ${file.name} (${file.length()} bytes)"
}

// Organize files by extension into subdirectories
println "\n--- Organizing by extension ---"
def counts = [:]
workDir.eachFile { file ->
    if (file.isFile()) {
        def ext = file.name.tokenize('.').last()
        def targetDir = new File(workDir, ext)
        targetDir.mkdirs()

        def target = new File(targetDir, file.name)
        file.renameTo(target)
        counts[ext] = (counts[ext] ?: 0) + 1
        println "  Moved ${file.name} -> ${ext}/"
    }
}

println "\n--- Summary ---"
counts.each { ext, count ->
    println "  .${ext}: ${count} file(s)"
}

// Cleanup
workDir.deleteDir()
println "Cleaned up temp directory."

Output

Working directory: /tmp/groovy_demo_1234567890

--- All files ---
  report.txt (30 bytes)
  data.csv (28 bytes)
  notes.txt (29 bytes)
  config.json (31 bytes)
  backup.csv (29 bytes)
  readme.md (29 bytes)
  settings.json (33 bytes)

--- Organizing by extension ---
  Moved report.txt -> txt/
  Moved data.csv -> csv/
  Moved notes.txt -> txt/
  Moved config.json -> json/
  Moved backup.csv -> csv/
  Moved readme.md -> md/
  Moved settings.json -> json/

--- Summary ---
  .txt: 2 file(s)
  .csv: 2 file(s)
  .json: 2 file(s)
  .md: 1 file(s)
Cleaned up temp directory.

What happened here: Groovy’s File enhancements make directory traversal trivial. The eachFile() method iterates over directory contents, and setting file.text writes content in one line. For deeper coverage of file I/O, check our Groovy File Read, Write, Delete guide.

Example 4: CSV Processing Script

What we’re doing: Parsing a CSV file and generating a summary report – a very common automation task.

Example 4: CSV Processing Automation

// CSV processing without any external libraries

def csvContent = """\
name,department,salary,city
Alice,Engineering,95000,New York
Bob,Marketing,72000,London
Charlie,Engineering,88000,Tokyo
Diana,Marketing,76000,Paris
Eve,Engineering,102000,New York
Frank,Sales,68000,London
Grace,Sales,71000,Tokyo
"""

def tempFile = File.createTempFile('employees', '.csv')
tempFile.text = csvContent
tempFile.deleteOnExit()

// Parse CSV
def lines = tempFile.readLines()
def headers = lines[0].split(',')
def records = lines.drop(1).findAll { it.trim() }.collect { line ->
    def values = line.split(',')
    [headers, values].transpose().collectEntries()
}

println "Total employees: ${records.size()}"
println "=" * 40

// Group by department
def byDept = records.groupBy { it.department }
byDept.each { dept, employees ->
    def avgSalary = employees.collect { it.salary as int }.average()
    println "\n${dept} (${employees.size()} people)"
    println "  Average salary: \$${String.format('%,.0f', avgSalary)}"
    employees.each { e ->
        println "  - ${e.name} in ${e.city}: \$${e.salary}"
    }
}

// Find highest paid
def topEarner = records.max { it.salary as int }
println "\nHighest paid: ${topEarner.name} (\$${topEarner.salary})"

// City distribution
println "\nEmployees by city:"
records.groupBy { it.city }.each { city, emps ->
    println "  ${city}: ${emps*.name.join(', ')}"
}

Output

Total employees: 7
========================================

Engineering (3 people)
  Average salary: $95,000
  - Alice in New York: $95000
  - Charlie in Tokyo: $88000
  - Eve in New York: $102000

Marketing (2 people)
  Average salary: $74,000
  - Bob in London: $72000
  - Diana in Paris: $76000

Sales (2 people)
  Average salary: $69,500
  - Frank in London: $68000
  - Grace in Tokyo: $71000

Highest paid: Eve ($102000)

Employees by city:
  New York: Alice, Eve
  London: Bob, Frank
  Tokyo: Charlie, Grace
  Paris: Diana

What happened here: We parsed CSV without any library. The transpose() method zips headers with values, and collectEntries() turns the pairs into a map. Then groupBy(), max(), and the spread operator (*.) handle all the reporting logic. For production CSV work with quoted fields, see our Groovy CSV Parsing tutorial.

Example 5: JSON Config Processing Script

What we’re doing: Reading a JSON configuration file, validating it, and generating environment-specific settings – the kind of task you’d run before a deployment.

Example 5: JSON Config Automation

import groovy.json.JsonSlurper
import groovy.json.JsonOutput

// Simulate a JSON config file
def configJson = '''
{
    "app": {
        "name": "MyService",
        "version": "2.1.0",
        "port": 8080
    },
    "database": {
        "host": "localhost",
        "port": 5432,
        "name": "myapp_db"
    },
    "features": {
        "caching": true,
        "logging": true,
        "debug": false
    },
    "environments": {
        "dev": {"database.host": "dev-db.local", "features.debug": true},
        "prod": {"database.host": "prod-db.cluster", "app.port": 443}
    }
}
'''

def config = new JsonSlurper().parseText(configJson)
def env = 'prod'  // Simulating command-line arg

println "Application: ${config.app.name} v${config.app.version}"
println "Target environment: ${env}"
println "-" * 40

// Apply environment overrides
def overrides = config.environments[env] ?: [:]
println "Applying ${overrides.size()} override(s)..."
overrides.each { key, value ->
    println "  ${key} = ${value}"
}

// Validate required fields
def required = ['app.name', 'app.version', 'database.host', 'database.port']
def missing = required.findAll { field ->
    def parts = field.split('\\.')
    def val = config
    parts.each { p -> val = val?."${p}" }
    val == null
}

if (missing) {
    println "ERROR: Missing required fields: ${missing}"
} else {
    println "All required fields present."
}

// Generate final config summary
def summary = [
    application: "${config.app.name}:${config.app.version}",
    database: "${overrides['database.host'] ?: config.database.host}:${config.database.port}",
    features: config.features.findAll { k, v -> v }.collect { k, v -> k }
]

println "\n--- Final Configuration ---"
println JsonOutput.prettyPrint(JsonOutput.toJson(summary))

Output

Application: MyService v2.1.0
Target environment: prod
----------------------------------------
Applying 2 override(s)...
  database.host = prod-db.cluster
  app.port = 443
All required fields present.

--- Final Configuration ---
{
    "application": "MyService:2.1.0",
    "database": "prod-db.cluster:5432",
    "features": [
        "caching",
        "logging"
    ]
}

What happened here: We loaded a JSON config, applied environment-specific overrides, validated required fields, and generated a deployment summary – all in about 40 lines of Groovy. No build.gradle, no project structure. Just a single script file.

Example 6: Database Automation Script

What we’re doing: Automating database operations – creating tables, inserting data, querying, and generating a report. This uses Groovy’s built-in groovy.sql.Sql with an embedded H2 database.

Example 6: Database Automation

@Grab('com.h2database:h2:2.2.224')
import groovy.sql.Sql

def db = Sql.newInstance('jdbc:h2:mem:automation', 'sa', '', 'org.h2.Driver')

// Create table
db.execute '''
    CREATE TABLE tasks (
        id INT AUTO_INCREMENT PRIMARY KEY,
        title VARCHAR(100),
        status VARCHAR(20),
        priority INT,
        created_date DATE DEFAULT CURRENT_DATE
    )
'''

// Batch insert
def tasks = [
    ['Setup CI pipeline', 'done', 1],
    ['Write unit tests', 'in_progress', 2],
    ['Deploy to staging', 'pending', 1],
    ['Code review', 'done', 3],
    ['Update documentation', 'pending', 2],
    ['Fix login bug', 'in_progress', 1],
    ['Database migration', 'done', 1],
    ['Performance testing', 'pending', 2]
]

tasks.each { title, status, priority ->
    db.executeInsert(
        'INSERT INTO tasks (title, status, priority) VALUES (?, ?, ?)',
        [title, status, priority]
    )
}
println "Inserted ${tasks.size()} tasks."

// Query and report
println "\n--- Task Report ---"
def rows = db.rows('SELECT * FROM tasks ORDER BY priority, status')
rows.each { row ->
    def icon = [done: '[DONE]', in_progress: '[WIP] ', pending: '[TODO]'][row.status]
    println "  ${icon} P${row.priority} - ${row.title}"
}

// Summary by status
println "\n--- Summary ---"
db.eachRow('SELECT status, COUNT(*) as cnt FROM tasks GROUP BY status ORDER BY cnt DESC') { row ->
    println "  ${row.status}: ${row.cnt} task(s)"
}

// High priority pending items
println "\n--- Action Required (Priority 1, Not Done) ---"
db.eachRow("SELECT title FROM tasks WHERE priority = 1 AND status != 'done'") { row ->
    println "  ! ${row.title}"
}

db.close()
println "\nDatabase automation complete."

Output

Inserted 8 tasks.

--- Task Report ---
  [DONE] P1 - Setup CI pipeline
  [DONE] P1 - Database migration
  [WIP]  P1 - Fix login bug
  [TODO] P1 - Deploy to staging
  [DONE] P3 - Code review
  [WIP]  P2 - Write unit tests
  [TODO] P2 - Update documentation
  [TODO] P2 - Performance testing

--- Summary ---
  pending: 3 task(s)
  done: 3 task(s)
  in_progress: 2 task(s)

--- Action Required (Priority 1, Not Done) ---
  ! Deploy to staging
  ! Fix login bug

Database automation complete.

What happened here: The @Grab annotation pulled in the H2 database driver at runtime – zero configuration needed. Groovy’s Sql class makes database work incredibly concise. For more on database automation, see our Groovy SQL Database Connection guide.

Example 7: Log File Analysis Script

What we’re doing: Parsing a log file, extracting patterns, counting error types, and generating an analysis report – a task every ops engineer needs.

Example 7: Log File Analysis

// Simulate a log file
def logContent = """\
2026-03-09 08:01:12 INFO  [main] AppServer - Server starting on port 8080
2026-03-09 08:01:15 INFO  [main] Database - Connection pool initialized (size=10)
2026-03-09 08:02:30 WARN  [http-1] AuthService - Failed login attempt for user: bob
2026-03-09 08:03:45 ERROR [http-2] OrderService - NullPointerException: order.customer is null
2026-03-09 08:04:10 INFO  [http-3] UserService - User alice logged in successfully
2026-03-09 08:05:22 ERROR [http-1] PaymentService - Connection timeout to payment gateway
2026-03-09 08:06:00 WARN  [http-2] AuthService - Failed login attempt for user: bob
2026-03-09 08:07:15 INFO  [http-4] OrderService - Order #1001 created for alice
2026-03-09 08:08:30 ERROR [http-2] OrderService - NullPointerException: order.customer is null
2026-03-09 08:09:45 ERROR [http-1] PaymentService - Connection timeout to payment gateway
2026-03-09 08:10:00 WARN  [scheduler] CleanupJob - Temp files older than 7 days found: 23 files
2026-03-09 08:11:30 INFO  [http-3] UserService - User charlie logged in successfully
2026-03-09 08:12:00 ERROR [http-2] PaymentService - Connection timeout to payment gateway
"""

def logFile = File.createTempFile('app', '.log')
logFile.text = logContent
logFile.deleteOnExit()

def lines = logFile.readLines().findAll { it.trim() }
println "Analyzing ${lines.size()} log entries..."
println "=" * 50

// Parse log entries with regex
def pattern = ~/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (\w+)\s+\[(.+?)\] (\w+) - (.+)/
def entries = lines.collect { line ->
    def matcher = pattern.matcher(line)
    if (matcher.matches()) {
        [timestamp: matcher[0][1], level: matcher[0][2],
         thread: matcher[0][3], source: matcher[0][4], message: matcher[0][5]]
    }
}.findAll { it != null }

// Count by level
println "\n--- Log Levels ---"
entries.groupBy { it.level }.each { level, msgs ->
    def bar = '#' * msgs.size()
    println "  ${level.padRight(6)} ${bar} (${msgs.size()})"
}

// Error breakdown
println "\n--- Error Details ---"
def errors = entries.findAll { it.level == 'ERROR' }
errors.groupBy { it.source }.each { source, errs ->
    println "  ${source}: ${errs.size()} error(s)"
    errs.groupBy { it.message }.each { msg, occurrences ->
        println "    -> ${msg} (x${occurrences.size()})"
    }
}

// Warning patterns
println "\n--- Warnings ---"
entries.findAll { it.level == 'WARN' }.each { w ->
    println "  [${w.timestamp}] ${w.message}"
}

println "\nAnalysis complete."

Output

Analyzing 13 log entries...
==================================================

--- Log Levels ---
  INFO   ##### (5)
  WARN   ### (3)
  ERROR  ##### (5)

--- Error Details ---
  OrderService: 2 error(s)
    -> NullPointerException: order.customer is null (x2)
  PaymentService: 3 error(s)
    -> Connection timeout to payment gateway (x3)

--- Warnings ---
  [2026-03-09 08:02:30] Failed login attempt for user: bob
  [2026-03-09 08:06:00] Failed login attempt for user: bob
  [2026-03-09 08:10:00] Temp files older than 7 days found: 23 files

Analysis complete.

What happened here: Groovy’s regex support with the ~/pattern/ syntax and matcher makes log parsing simple. The groupBy() method does the heavy lifting for categorization. In a real scenario, you’d point this at a live log file or pipe it into a scheduled task.

Example 8: Backup Script with Timestamps

What we’re doing: Creating a backup script that copies files, adds timestamps, and maintains a backup manifest – the kind of utility you’d schedule as a cron job.

Example 8: Backup Automation Script

import java.nio.file.Files
import java.nio.file.StandardCopyOption

def timestamp = new Date().format('yyyyMMdd_HHmmss')

// Setup source and backup directories
def sourceDir = File.createTempDir('source_')
def backupDir = File.createTempDir('backup_')
def backupTarget = new File(backupDir, "backup_${timestamp}")
backupTarget.mkdirs()

// Create sample source files
['config.properties', 'data.csv', 'app.log', 'users.json'].each { name ->
    new File(sourceDir, name).text = "Content of ${name} - ${new Date()}"
}

println "Backup Script"
println "=" * 40
println "Source:  ${sourceDir.absolutePath}"
println "Target:  ${backupTarget.absolutePath}"
println "Started: ${new Date().format('yyyy-MM-dd HH:mm:ss')}"
println ""

// Perform backup
def manifest = []
def totalBytes = 0L

sourceDir.eachFile { file ->
    if (file.isFile()) {
        def dest = new File(backupTarget, file.name)
        Files.copy(file.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING)

        def size = file.length()
        totalBytes += size
        manifest << [
            file: file.name,
            size: size,
            modified: new Date(file.lastModified()).format('yyyy-MM-dd HH:mm:ss')
        ]
        println "  Copied: ${file.name} (${size} bytes)"
    }
}

// Write manifest
def manifestFile = new File(backupTarget, 'MANIFEST.txt')
manifestFile.text = "Backup Manifest - ${timestamp}\n"
manifestFile.append("=" * 40 + "\n")
manifest.each { entry ->
    manifestFile.append("${entry.file} | ${entry.size} bytes | ${entry.modified}\n")
}
manifestFile.append("\nTotal: ${manifest.size()} files, ${totalBytes} bytes\n")

println "\n--- Backup Summary ---"
println "Files backed up: ${manifest.size()}"
println "Total size: ${totalBytes} bytes"
println "Manifest: ${manifestFile.name}"

// Verify backup
def verified = backupTarget.listFiles().findAll { it.name != 'MANIFEST.txt' }.size()
println "Verification: ${verified}/${manifest.size()} files confirmed"
println "Status: ${verified == manifest.size() ? 'SUCCESS' : 'FAILED'}"

// Cleanup
sourceDir.deleteDir()
backupDir.deleteDir()

Output

Backup Script
========================================
Source:  /tmp/source_1234567890
Target:  /tmp/backup_5678901234/backup_20260309_101530
Started: 2026-03-09 10:15:30

  Copied: config.properties (52 bytes)
  Copied: data.csv (44 bytes)
  Copied: app.log (44 bytes)
  Copied: users.json (47 bytes)

--- Backup Summary ---
Files backed up: 4
Total size: 187 bytes
Manifest: MANIFEST.txt
Verification: 4/4 files confirmed
Status: SUCCESS

What happened here: We used Java’s Files.copy() for reliable file copying, added timestamp-based backup directories, generated a manifest file, and verified the backup. This script could be extended to support compression, remote storage, or email notifications. For more on executing system-level tasks like tar or zip, see Groovy Process Execution.

Example 9: Build Automation and Environment Setup

What we’re doing: Writing a script that checks system prerequisites, validates environment variables, and prepares a project for building.

Example 9: Build Automation Script

// Build pre-check and environment setup script

println "Build Environment Checker"
println "=" * 40

// Check Java version
def javaVersion = System.getProperty('java.version')
def javaHome = System.getProperty('java.home')
def groovyVersion = GroovySystem.version

println "\n--- Runtime ---"
println "  Java:   ${javaVersion} (${javaHome})"
println "  Groovy: ${groovyVersion}"
println "  OS:     ${System.getProperty('os.name')} ${System.getProperty('os.arch')}"

// Check required environment variables
println "\n--- Environment Variables ---"
def requiredVars = ['JAVA_HOME', 'GROOVY_HOME', 'PATH']
def optionalVars = ['GRADLE_HOME', 'M2_HOME', 'GRAILS_HOME']

requiredVars.each { varName ->
    def value = System.getenv(varName)
    def status = value ? 'OK' : 'MISSING'
    def display = value ? value.take(50) + (value.length() > 50 ? '...' : '') : 'NOT SET'
    println "  [${status.padRight(7)}] ${varName} = ${display}"
}

optionalVars.each { varName ->
    def value = System.getenv(varName)
    def display = value ? value.take(50) : '(not set)'
    println "  [OPTIONAL] ${varName} = ${display}"
}

// Check available memory
def runtime = Runtime.getRuntime()
def maxMem = runtime.maxMemory() / (1024 * 1024)
def freeMem = runtime.freeMemory() / (1024 * 1024)
def totalMem = runtime.totalMemory() / (1024 * 1024)

println "\n--- Memory ---"
println "  Max:   ${String.format('%.0f', maxMem)} MB"
println "  Total: ${String.format('%.0f', totalMem)} MB"
println "  Free:  ${String.format('%.0f', freeMem)} MB"
println "  CPUs:  ${runtime.availableProcessors()}"

// Check disk space
def roots = File.listRoots()
println "\n--- Disk Space ---"
roots.each { root ->
    def totalGB = root.totalSpace / (1024 * 1024 * 1024)
    def freeGB = root.usableSpace / (1024 * 1024 * 1024)
    if (totalGB > 0) {
        def usedPct = ((totalGB - freeGB) / totalGB * 100) as int
        println "  ${root.path}: ${String.format('%.1f', freeGB)} GB free of ${String.format('%.1f', totalGB)} GB (${usedPct}% used)"
    }
}

// Generate build readiness report
def issues = []
if (!System.getenv('JAVA_HOME')) issues << 'JAVA_HOME not set'
if (maxMem < 256) issues << 'Less than 256 MB max heap'
def javaMajor = javaVersion.tokenize('.')[0] as int
if (javaMajor < 17) issues << "Java ${javaMajor} detected; Java 17+ required"

println "\n--- Build Readiness ---"
if (issues.isEmpty()) {
    println "  STATUS: READY TO BUILD"
} else {
    println "  STATUS: ISSUES FOUND"
    issues.each { println "  ! ${it}" }
}

Output

Build Environment Checker
========================================

--- Runtime ---
  Java:   17.0.2 (/usr/lib/jvm/java-17/jre)
  Groovy: 5.0.0
  OS:     Linux amd64

--- Environment Variables ---
  [OK     ] JAVA_HOME = /usr/lib/jvm/java-17
  [OK     ] GROOVY_HOME = /opt/groovy-5.0.0
  [OK     ] PATH = /usr/local/bin:/usr/bin:/bin:/opt/groovy-5.0.0/b...

  [OPTIONAL] GRADLE_HOME = /opt/gradle-8.5
  [OPTIONAL] M2_HOME = (not set)
  [OPTIONAL] GRAILS_HOME = (not set)

--- Memory ---
  Max:   256 MB
  Total: 64 MB
  Free:  55 MB
  CPUs:  4

--- Disk Space ---
  /: 42.3 GB free of 100.0 GB (57% used)

--- Build Readiness ---
  STATUS: READY TO BUILD

What happened here: This is a pre-flight check script you’d run before a build or deployment. It validates the environment, checks memory and disk, and reports any issues. You could easily extend this to verify network connectivity, check service health endpoints, or validate configuration files.

Example 10: Report Generation Script

What we’re doing: Generating an HTML report from structured data – combining data processing with file output for a polished result.

Example 10: HTML Report Generator

import groovy.xml.MarkupBuilder

// Sample data - could come from a database, CSV, or API
def projectData = [
    title: "Weekly Sprint Report",
    date: new Date().format('MMMM dd, yyyy'),
    team: "Engineering",
    tasks: [
        [name: 'API Refactoring', status: 'done', hours: 16, owner: 'Alice'],
        [name: 'Unit Tests', status: 'done', hours: 8, owner: 'Bob'],
        [name: 'Database Migration', status: 'in_progress', hours: 12, owner: 'Charlie'],
        [name: 'UI Redesign', status: 'in_progress', hours: 20, owner: 'Diana'],
        [name: 'Performance Audit', status: 'pending', hours: 0, owner: 'Eve']
    ]
]

// Generate HTML report
def writer = new StringWriter()
def html = new MarkupBuilder(writer)

html.html {
    head { title(projectData.title) }
    body {
        h1(projectData.title)
        p("Date: ${projectData.date} | Team: ${projectData.team}")

        h2("Task Summary")
        table(border: '1', cellpadding: '8', cellspacing: '0') {
            tr {
                th('Task'); th('Owner'); th('Status'); th('Hours')
            }
            projectData.tasks.each { task ->
                tr {
                    td(task.name)
                    td(task.owner)
                    td(task.status.replace('_', ' ').capitalize())
                    td(task.hours.toString())
                }
            }
        }

        h2("Statistics")
        def totalHours = projectData.tasks.sum { it.hours }
        def byStatus = projectData.tasks.groupBy { it.status }
        ul {
            li("Total hours logged: ${totalHours}")
            byStatus.each { status, tasks ->
                li("${status.replace('_', ' ').capitalize()}: ${tasks.size()} task(s)")
            }
        }
    }
}

// Save report
def reportFile = File.createTempFile('report_', '.html')
reportFile.text = "<!DOCTYPE html>\n${writer.toString()}"
reportFile.deleteOnExit()

println "Report generated: ${reportFile.absolutePath}"
println "Size: ${reportFile.length()} bytes"
println "\n--- Console Summary ---"
println "Project: ${projectData.title}"
println "Tasks: ${projectData.tasks.size()}"
println "Total Hours: ${projectData.tasks.sum { it.hours }}"
projectData.tasks.groupBy { it.status }.each { status, tasks ->
    println "  ${status}: ${tasks*.name.join(', ')}"
}

Output

Report generated: /tmp/report_1234567890.html
Size: 892 bytes

--- Console Summary ---
Project: Weekly Sprint Report
Tasks: 5
Total Hours: 56
  done: API Refactoring, Unit Tests
  in_progress: Database Migration, UI Redesign
  pending: Performance Audit

What happened here: Groovy’s MarkupBuilder generates clean HTML using a builder DSL – no string concatenation or template files needed. This approach works for HTML reports, XML configuration files, or any structured markup output. The data could just as easily come from a database query or an API call.

Example 11: Scheduled Task Simulation with Timers

What we’re doing: Simulating a scheduled task runner that executes jobs at intervals – useful for monitoring, cleanup, or data sync scripts.

Example 11: Scheduled Task Runner

import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

// Define tasks as closures
def tasks = [
    'Health Check': {
        def memUsed = (Runtime.runtime.totalMemory() - Runtime.runtime.freeMemory()) / (1024 * 1024)
        println "  [Health] Memory used: ${String.format('%.1f', memUsed)} MB - Status: OK"
    },
    'Cleanup': {
        def count = (Math.random() * 10) as int
        println "  [Cleanup] Removed ${count} temp files"
    },
    'Status Report': {
        def uptime = ManagementFactory.getRuntimeMXBean().uptime
        println "  [Status] JVM uptime: ${uptime} ms, Threads: ${Thread.activeCount()}"
    }
]

import java.lang.management.ManagementFactory

println "Task Scheduler Demo"
println "=" * 40
println "Registered ${tasks.size()} tasks"
println ""

// Run tasks in sequence (simulating 3 cycles)
3.times { cycle ->
    println "--- Cycle ${cycle + 1} [${new Date().format('HH:mm:ss')}] ---"
    tasks.each { name, task ->
        try {
            task()
        } catch (Exception e) {
            println "  [ERROR] ${name}: ${e.message}"
        }
    }
    if (cycle < 2) {
        Thread.sleep(100)  // Short pause between cycles for demo
    }
    println ""
}

println "Scheduler completed 3 cycles."
println "\nTip: For real scheduling, use java.util.Timer or"
println "ScheduledExecutorService for production-grade task scheduling."

Output

Task Scheduler Demo
========================================
Registered 3 tasks

--- Cycle 1 [10:15:30] ---
  [Health] Memory used: 12.3 MB - Status: OK
  [Cleanup] Removed 7 temp files
  [Status] JVM uptime: 1250 ms, Threads: 2

--- Cycle 2 [10:15:30] ---
  [Health] Memory used: 12.5 MB - Status: OK
  [Cleanup] Removed 3 temp files
  [Status] JVM uptime: 1355 ms, Threads: 2

--- Cycle 3 [10:15:30] ---
  [Health] Memory used: 12.4 MB - Status: OK
  [Cleanup] Removed 5 temp files
  [Status] JVM uptime: 1460 ms, Threads: 2

Scheduler completed 3 cycles.

Tip: For real scheduling, use java.util.Timer or
ScheduledExecutorService for production-grade task scheduling.

What happened here: We defined tasks as Groovy closures stored in a map, then ran them in cycles. In production, you’d replace the loop with a ScheduledExecutorService or a Timer. Groovy closures make task definitions incredibly clean – each task is just a block of code you can store, pass around, and execute on demand.

Example 12: @Grab for Zero-Setup Automation Scripts

What we’re doing: Using @Grab to pull in external libraries at runtime – no project setup, no build files. Just a single script that handles its own dependencies.

Example 12: @Grab Zero-Setup Script

// This script pulls in Apache Commons CSV at runtime
// No build.gradle, no pom.xml, no project setup needed!
@Grab('org.apache.commons:commons-csv:1.10.0')
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVPrinter

// Generate a professional CSV report using Apache Commons CSV
def reportFile = File.createTempFile('sales_report_', '.csv')
reportFile.deleteOnExit()

def salesData = [
    [region: 'North', product: 'Widget A', q1: 15000, q2: 18000, q3: 22000, q4: 19500],
    [region: 'South', product: 'Widget A', q1: 12000, q2: 14500, q3: 16000, q4: 17000],
    [region: 'North', product: 'Widget B', q1: 8000,  q2: 9500,  q3: 11000, q4: 10500],
    [region: 'South', product: 'Widget B', q1: 6500,  q2: 7200,  q3: 8800,  q4: 9000],
    [region: 'East',  product: 'Widget A', q1: 20000, q2: 23000, q3: 25000, q4: 27000],
]

// Write CSV with Apache Commons CSV
reportFile.withWriter { writer ->
    def printer = new CSVPrinter(writer, CSVFormat.DEFAULT
        .builder()
        .setHeader('Region', 'Product', 'Q1', 'Q2', 'Q3', 'Q4', 'Total')
        .build())

    salesData.each { row ->
        def total = row.q1 + row.q2 + row.q3 + row.q4
        printer.printRecord(row.region, row.product, row.q1, row.q2, row.q3, row.q4, total)
    }
    printer.flush()
}

println "Sales Report Generator (powered by @Grab)"
println "=" * 50

// Read back and display
println "\nGenerated CSV:"
println reportFile.text

// Analysis
println "--- Analysis ---"
def grandTotal = salesData.collect { it.q1 + it.q2 + it.q3 + it.q4 }.sum()
println "Grand total: \$${String.format('%,d', grandTotal)}"

salesData.groupBy { it.region }.each { region, rows ->
    def regionTotal = rows.collect { it.q1 + it.q2 + it.q3 + it.q4 }.sum()
    println "${region}: \$${String.format('%,d', regionTotal)}"
}

println "\nBest performing product-region:"
def best = salesData.max { it.q1 + it.q2 + it.q3 + it.q4 }
println "  ${best.product} in ${best.region}: \$${String.format('%,d', best.q1 + best.q2 + best.q3 + best.q4)}"

println "\nFile: ${reportFile.absolutePath}"
println "\nDependency pulled automatically by @Grab - zero setup!"

Output

Sales Report Generator (powered by @Grab)
==================================================

Generated CSV:
Region,Product,Q1,Q2,Q3,Q4,Total
North,Widget A,15000,18000,22000,19500,74500
South,Widget A,12000,14500,16000,17000,59500
North,Widget B,8000,9500,11000,10500,39000
South,Widget B,6500,7200,8800,9000,31500
East,Widget A,20000,23000,25000,27000,95000

--- Analysis ---
Grand total: $299,500
North: $113,500
South: $91,000
East: $95,000

Best performing product-region:
  Widget A in East: $95,000

File: /tmp/sales_report_1234567890.csv

Dependency pulled automatically by @Grab - zero setup!

What happened here: The @Grab('org.apache.commons:commons-csv:1.10.0') annotation told Groovy to download Apache Commons CSV from Maven Central before running the script. The first run downloads the JAR; subsequent runs use the cached version. This is the killer feature for Groovy automation – you can use any Java library in a single-file script with zero project scaffolding. For more on @Grab, see the official Grape documentation.

Edge Cases and Best Practices

Edge Case: Script vs Class Mode

One thing that catches people: if your .groovy file contains a class with a main() method, Groovy treats it as a compiled class, not a script. You can’t mix top-level script code with class definitions in the same file (unless the class doesn’t have a main).

Script vs Class Mode

// THIS WORKS - helper class inside a script
class MathHelper {
    static int square(int n) { n * n }
    static int cube(int n) { n * n * n }
}

// Top-level script code using the class
[2, 3, 4, 5].each { n ->
    println "${n}: square=${MathHelper.square(n)}, cube=${MathHelper.cube(n)}"
}

// THIS WOULD NOT WORK in the same file:
// class App {
//     static void main(String[] args) { ... }  // Conflicts with script mode!
// }

Output

2: square=4, cube=8
3: square=9, cube=27
4: square=16, cube=64
5: square=25, cube=125

Best Practices for Groovy Automation Scripts

DO:

  • Use @Grab for dependencies – keeps scripts self-contained and portable
  • Use CliBuilder for command-line argument parsing – gives you a professional CLI interface for free
  • Wrap risky operations (file I/O, network, database) in try-catch blocks
  • Use File.createTempFile() and deleteOnExit() for temporary files
  • Add a shebang line (#!/usr/bin/env groovy) to make scripts directly executable on Unix systems
  • Use System.exit(0) or System.exit(1) for proper exit codes when integrating with other tools

DON’T:

  • Hard-code file paths – use CliBuilder or environment variables instead
  • Ignore exit codes from external processes – always check process.exitValue()
  • Use @Grab in production server code – it’s meant for scripts, not deployed applications
  • Forget to close database connections and file handles – use withCloseable or try-with-resources

Performance and Practical Tips

Groovy scripts start up faster than a full Spring Boot application, but they’re still JVM-based. Here are some things to keep in mind:

  • JVM startup overhead: The first groovy invocation takes 1-3 seconds for JVM startup. For frequently-run scripts, consider using groovysh (the Groovy shell) or keep a Groovy daemon running.
  • @Grab caching: The first @Grab call downloads JARs to ~/.groovy/grapes/. Subsequent runs use the cache. If you’re running scripts in CI/CD, pre-populate the cache or mount a shared volume.
  • Memory for large files: When processing large CSV or log files (100MB+), use streaming instead of readLines(). Use file.eachLine { } to process line by line without loading the entire file into memory.
  • Script compilation: Groovy compiles scripts to bytecode on first run. Use groovyc to pre-compile if startup time is critical.
  • Shebang scripts: On Linux and macOS, add #!/usr/bin/env groovy as the first line and chmod +x script.groovy to make it directly executable from the command line.

Pro Tip: For scripts that need to run on machines without Groovy installed, you can compile a Groovy script into a runnable JAR with all dependencies bundled. Use groovyc combined with a build tool, or use Groovy’s compiler documentation for details on static compilation and packaging.

Conclusion

Groovy scripting gives you the best of both worlds: the expressiveness and simplicity of a scripting language with the power of the entire Java ecosystem. You can write a file organizer in 20 lines, parse logs with regex and collection methods, automate databases with zero-config dependencies, and generate reports with builder DSLs – all without creating a project, writing a build.gradle, or compiling anything.

The real magic comes from combining features: @Grab for instant dependencies, CliBuilder for professional CLIs, groovy.sql.Sql for database work, JsonSlurper/MarkupBuilder for data processing, and Groovy’s file I/O enhancements for filesystem automation. Each script is self-contained, portable, and immediately runnable. That’s what makes Groovy automation so effective.

Summary

  • Groovy scripts need no main() method, no class declaration, and no compilation step
  • CliBuilder provides built-in command-line argument parsing with automatic help generation
  • @Grab pulls Maven dependencies at runtime – zero setup, fully self-contained scripts
  • Groovy’s file I/O, regex, and collection methods make log analysis, CSV processing, and report generation concise and readable
  • You can access the entire Java ecosystem from a script – databases, HTTP clients, XML/JSON libraries, and more
  • For large file processing, use streaming methods like eachLine() instead of readLines()

If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.

Up next: Groovy 5 New Features – What’s Changed

Frequently Asked Questions

What makes Groovy good for scripting and automation?

Groovy runs on the JVM but doesn’t require a main() method, class declarations, or compilation. You write top-level code in a .groovy file and run it directly with the groovy command. It has built-in features like CliBuilder for argument parsing, @Grab for pulling dependencies at runtime, and enhanced file I/O – all making it ideal for quick automation tasks while still having access to the entire Java ecosystem.

How do I parse command-line arguments in a Groovy script?

Use Groovy’s built-in CliBuilder. Define options with short and long flags, mark required arguments, and call cli.parse(args). The args variable is automatically available in Groovy scripts. CliBuilder also generates help messages automatically when you call cli.usage().

What is @Grab in Groovy and how does it work?

@Grab is Groovy’s built-in dependency manager (part of the Grape system). You annotate your script with @Grab('group:artifact:version') and Groovy automatically downloads the JAR from Maven Central before running the script. Dependencies are cached in ~/.groovy/grapes/. This lets you use any Java library in a single-file script without a build tool.

Can I run Groovy scripts without installing Groovy?

Yes, there are several options. You can use groovyc to compile scripts into bytecode and package them as executable JARs. You can also use SDKMan (sdk install groovy) for easy installation, or run Groovy scripts in Docker containers with the official Groovy image. For CI/CD pipelines, most build tools like Gradle include Groovy support built in.

How does Groovy scripting compare to Python for automation?

Groovy and Python are both excellent for scripting. Groovy’s advantage is smooth Java interoperability – you can use any Java library directly, which is huge if you’re already in a JVM ecosystem. Python has a larger scripting community and more OS-level libraries. Groovy scripts tend to be more concise than equivalent Java code but may have slightly longer startup times than Python due to JVM initialization.

Previous in Series: Groovy Spock Testing – Getting Started

Next in Series: Groovy 5 New Features – What’s Changed

Related Topics You Might Like:

This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *