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
Table of Contents
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
@Grabfor dependencies – keeps scripts self-contained and portable - Use
CliBuilderfor 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()anddeleteOnExit()for temporary files - Add a shebang line (
#!/usr/bin/env groovy) to make scripts directly executable on Unix systems - Use
System.exit(0)orSystem.exit(1)for proper exit codes when integrating with other tools
DON’T:
- Hard-code file paths – use
CliBuilderor environment variables instead - Ignore exit codes from external processes – always check
process.exitValue() - Use
@Grabin production server code – it’s meant for scripts, not deployed applications - Forget to close database connections and file handles – use
withCloseableor 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
groovyinvocation takes 1-3 seconds for JVM startup. For frequently-run scripts, consider usinggroovysh(the Groovy shell) or keep a Groovy daemon running. - @Grab caching: The first
@Grabcall 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(). Usefile.eachLine { }to process line by line without loading the entire file into memory. - Script compilation: Groovy compiles scripts to bytecode on first run. Use
groovycto pre-compile if startup time is critical. - Shebang scripts: On Linux and macOS, add
#!/usr/bin/env groovyas the first line andchmod +x script.groovyto 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 CliBuilderprovides built-in command-line argument parsing with automatic help generation@Grabpulls 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 ofreadLines()
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.
Related Posts
Previous in Series: Groovy Spock Testing – Getting Started
Next in Series: Groovy 5 New Features – What’s Changed
Related Topics You Might Like:
- Groovy Process Execution – System Commands
- Groovy File Read, Write, Delete
- Groovy SQL Database Connection
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment