It has been really fun learning Groovy. As part of my learning process I reached out to James Strachan, the inventor and author of Groovy, and he accepted my connection request on LinkedIn. I then told him I was writing this article, sent him a private link, and asked for his approval. He replied with a thumbs up. So this article has been read and approved by James Strachan himself. :)
Welcome to the Groovy crash course! Whether you're coming from Java, Python, or another language, Groovy offers a powerful, expressive syntax that makes coding a joy. This guide will take you from zero to being productive in Groovy, with practical examples you can use immediately.
Groovy was created by James Strachan in 2003 and became an Apache project in 2015. It was designed to enhance Java with dynamic language features while maintaining seamless Java integration. Think of Groovy as "Java's cool younger sibling"--it runs on the JVM, can use any Java library, but with far less boilerplate and much more elegance.
Today, Groovy powers major projects like the Gradle build tool, Jenkins pipeline scripts, and countless enterprise applications. It's a mature, production-ready language backed by a vibrant community.
Groovy runs on the Java Virtual Machine (JVM), so you'll need Java installed first. Java 8 or higher is required, though Java 11+ is recommended.
Check if Java is installed:
java -version
If you don't have Java, download it from Adoptium (formerly AdoptOpenJDK) or Oracle.
Windows (using Chocolatey):
choco install groovy
macOS (using Homebrew):
brew install groovy
Linux (using SDKMAN):
curl -s "https://get.sdkman.io" | bash
sdk install groovy
Manual Installation:
Download from groovy-lang.org, extract, and add the bin directory to your PATH.
Verify installation:
groovy --version
You should see something like Groovy Version: 4.0.x JVM: ...
Create a file called hello.groovy:
println "Hello, Groovy!"
Run it:
groovy hello.groovy
Notice what's missing? No semicolons required, no public static void main, no System.out. Groovy is concise by design.
Groovy supports three types of comments:
// Single-line comment
/*
* Multi-line comment
* Great for longer explanations
*/
/**
* Groovydoc comment (like Javadoc)
* Used to document classes and methods
* @param name The person's name
* @return A greeting message
*/
String greet(String name) {
return "Hello, ${name}!"
}
Semicolons are optional in Groovy. Most developers omit them:
def name = "Alice"
def age = 30
println "Name: $name, Age: $age"
For method calls with parameters, parentheses are often optional:
println "Hello" // Same as println("Hello")
println("Hello", "World") // But needed for multiple arguments: println "Hello", "World"
The last expression in a method is automatically returned:
def add(a, b) {
a + b // Automatically returned
}
println add(5, 3) // Outputs: 8
defUse def for dynamic typing, or specify the type explicitly:
def dynamicVar = "I can be anything"
dynamicVar = 42 // Now it's a number
String explicitString = "I must be a String"
// explicitString = 42 // Would cause a type error
Groovy supports all Java primitive types plus adds convenience:
// Numbers
def integer = 42
def longNum = 42L
def floatingPoint = 3.14
def bigDecimal = 3.14G // Exact decimal arithmetic
def bigInteger = 42G
// Strings (covered in detail below)
def singleQuote = 'Simple string'
def doubleQuote = "String with $interpolation"
def multiLine = '''
Multi-line
string
'''
// Booleans
def isTrue = true
def isFalse = false
// null
def nothing = null
// Type checking
println integer.class // class java.lang.Integer
println floatingPoint.class // class java.math.BigDecimal (Groovy's default)
Important: Groovy uses BigDecimal for decimal literals by default, which means financial calculations are precise without extra code!
One of Groovy's killer features is string interpolation:
def name = "Alice"
def age = 30
// Single quotes: no interpolation
def simple = 'Hello, $name'
println simple // Outputs: Hello, $name
// Double quotes: interpolation!
def interpolated = "Hello, $name"
println interpolated // Outputs: Hello, Alice
// Expressions in strings
def info = "In 5 years, $name will be ${age + 5}"
println info // Outputs: In 5 years, Alice will be 35
// Multi-line strings
def multiLine = """
Name: $name
Age: $age
Status: ${age >= 18 ? 'Adult' : 'Minor'}
"""
println multiLine
def text = "Groovy is Groovy"
// Common operations
println text.length() // 16
println text.toUpperCase() // GROOVY IS GROOVY
println text.toLowerCase() // groovy is groovy
println text.contains("Groovy") // true
println text.startsWith("Groovy") // true
println text.endsWith("Groovy") // true
println text.replace("Groovy", "Great") // Great is Great
// Splitting and joining
def words = text.split(" ")
println words // [Groovy, is, Groovy]
println words.join("-") // Groovy-is-Groovy
// Trimming
def padded = " spaces "
println padded.trim() // "spaces"
// Substring
println text[0..5] // Groovy (range notation)
println text[0..-1] // Full string
Groovy makes collections delightful to work with.
// Creating lists
def emptyList = []
def numbers = [1, 2, 3, 4, 5]
def mixed = [1, "two", 3.0, true]
// Accessing elements
println numbers[0] // 1 (first element)
println numbers[-1] // 5 (last element)
println numbers[1..3] // [2, 3, 4] (range)
// Adding elements
numbers << 6 // Append operator
numbers.add(7)
println numbers // [1, 2, 3, 4, 5, 6, 7]
// Removing elements
numbers.remove(2) // Remove at index 2
println numbers // [1, 2, 4, 5, 6, 7]
// Useful operations
println numbers.size() // 6
println numbers.contains(4) // true
println numbers.first() // 1
println numbers.last() // 7
// Powerful functional methods
def doubled = numbers.collect { it * 2 }
println doubled // [2, 4, 8, 10, 12, 14]
def evens = numbers.findAll { it % 2 == 0 }
println evens // [2, 4, 6]
def sum = numbers.sum()
println sum // 25
// Creating maps
def emptyMap = [:]
def person = [
name: "Alice",
age: 30,
city: "New York"
]
// Accessing values
println person.name // Alice (dot notation)
println person['age'] // 30 (bracket notation)
// Adding/updating
person.email = "alice@example.com"
person['phone'] = "555-1234"
println person
// Checking keys
println person.containsKey('name') // true
println person.containsKey('address') // false
// Iterating
person.each { key, value ->
println "$key: $value"
}
// Keys and values
println person.keySet() // [name, age, city, email, phone]
println person.values() // [Alice, 30, New York, alice@example.com, 555-1234]
Ranges are a special Groovy feature:
// Numeric ranges
def numbers = 1..10 // Inclusive range
def exclusive = 1..<10 // Exclusive end
println numbers // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
println exclusive // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// Character ranges
def letters = 'a'..'e'
println letters // [a, b, c, d, e]
// Using in loops
for (i in 1..5) {
println "Number: $i"
}
// Check membership
println 5 in 1..10 // true
println 15 in 1..10 // false
def age = 25
// Basic if-else
if (age < 18) {
println "Minor"
} else if (age < 65) {
println "Adult"
} else {
println "Senior"
}
// Groovy truth: null, empty collections, zero are false
def name = ""
if (name) {
println "Name exists"
} else {
println "Name is empty" // This prints
}
// Ternary operator
def status = age >= 18 ? "Adult" : "Minor"
println status
// Elvis operator (null-safe)
def username = null
def displayName = username ?: "Guest"
println displayName // Guest
// Safe navigation operator
def person = null
println person?.name // null (no NullPointerException!)
// Switch with powerful matching
def grade = 'B'
switch (grade) {
case 'A':
println "Excellent!"
break
case 'B':
case 'C':
println "Good job!"
break
case 'D':
println "Needs improvement"
break
default:
println "See me after class"
}
// Switch with ranges
def score = 85
switch (score) {
case 90..100:
println "A"
break
case 80..<90:
println "B"
break
case 70..<80:
println "C"
break
default:
println "F"
}
// For loop (traditional)
for (int i = 0; i < 5; i++) {
println "Count: $i"
}
// For-in loop (Groovy style)
for (i in 1..5) {
println "Number: $i"
}
// Each method (most Groovy-esque)
(1..5).each { num ->
println "Value: $num"
}
// With lists
def fruits = ["apple", "banana", "cherry"]
fruits.each { fruit ->
println fruit.toUpperCase()
}
// With index
fruits.eachWithIndex { fruit, index ->
println "$index: $fruit"
}
// While loop
def count = 0
while (count < 5) {
println "Count: $count"
count++
}
// Times method (very Groovy!)
5.times {
println "Hello!"
}
// Times with index
5.times { i ->
println "Iteration: $i"
}
// Loop control
(1..10).each { num ->
if (num == 5) return // Continue to next iteration
println num
}
Warning: Be careful with while loops--always ensure your condition will eventually become false to avoid infinite loops!
Closures are one of Groovy's most powerful features. Think of them as anonymous functions that can capture variables from their surrounding scope.
// Basic closure
def greet = { name ->
println "Hello, $name!"
}
greet("Alice") // Hello, Alice!
// Closure with multiple parameters
def add = { a, b ->
a + b
}
println add(5, 3) // 8
// Implicit parameter 'it' for single-parameter closures
def square = { it * it }
println square(5) // 25
// Closures can capture variables
def multiplier = 10
def multiply = { num ->
num * multiplier
}
println multiply(5) // 50
// Using closures with collections
def numbers = [1, 2, 3, 4, 5]
// Filter
def evens = numbers.findAll { it % 2 == 0 }
println evens // [2, 4]
// Transform
def doubled = numbers.collect { it * 2 }
println doubled // [2, 4, 6, 8, 10]
// Reduce
def sum = numbers.inject(0) { acc, val -> acc + val }
println sum // 15
// Any/Every
println numbers.any { it > 3 } // true
println numbers.every { it > 0 } // true
Groovy makes file operations incredibly simple:
// Reading a file
def file = new File("example.txt")
// Read entire file as string
def content = file.text
println content
// Read as lines
def lines = file.readLines()
lines.each { line ->
println line
}
// Read with closure (automatically closes file)
file.eachLine { line, lineNumber ->
println "$lineNumber: $line"
}
// Writing to a file
def outputFile = new File("output.txt")
outputFile.text = "This replaces all content\n"
// Append to file
outputFile.append("This adds a line\n")
// Write multiple lines
outputFile.withWriter { writer ->
writer.writeLine("Line 1")
writer.writeLine("Line 2")
writer.writeLine("Line 3")
}
// Check file existence
if (file.exists()) {
println "File size: ${file.size()} bytes"
println "Last modified: ${new Date(file.lastModified())}"
}
// Working with directories
def dir = new File("mydir")
dir.mkdir() // Create directory
// List files
new File(".").eachFile { f ->
println f.name
}
// List files with filter
new File(".").eachFileMatch(~/.*.groovy/) { f ->
println f.name
}
Groovy uses try-catch-finally just like Java, but with Groovy syntax elegance:
// Basic try-catch
try {
def result = 10 / 0
} catch (ArithmeticException e) {
println "Cannot divide by zero: ${e.message}"
} finally {
println "This always executes"
}
// Multiple catch blocks
try {
def file = new File("nonexistent.txt")
def content = file.text
} catch (FileNotFoundException e) {
println "File not found: ${e.message}"
} catch (IOException e) {
println "IO error: ${e.message}"
} catch (Exception e) {
println "Unexpected error: ${e.message}"
}
// Groovy's withCloseable (auto-closes resources)
new File("data.txt").withReader { reader ->
def line
while ((line = reader.readLine()) != null) {
println line
}
} // Reader automatically closed
// Custom exceptions
class ValidationException extends Exception {
ValidationException(String message) {
super(message)
}
}
def validateAge(age) {
if (age < 0) {
throw new ValidationException("Age cannot be negative")
}
if (age > 150) {
throw new ValidationException("Age seems unrealistic")
}
return true
}
try {
validateAge(-5)
} catch (ValidationException e) {
println "Validation failed: ${e.message}"
}
// Basic method
def greet(name) {
"Hello, $name!" // Last expression is returned
}
println greet("Alice")
// With explicit types
String greetFormal(String name) {
return "Good day, $name"
}
// Default parameters
def power(base, exponent = 2) {
base ** exponent // ** is power operator
}
println power(5) // 25 (5^2)
println power(5, 3) // 125 (5^3)
// Named parameters (using a Map)
def createUser(Map params) {
println "Creating user:"
println " Name: ${params.name}"
println " Email: ${params.email}"
println " Age: ${params.age ?: 'Not specified'}"
}
createUser(name: "Alice", email: "alice@example.com", age: 30)
// Variable arguments
def sum(int... numbers) {
numbers.sum()
}
println sum(1, 2, 3, 4, 5) // 15
// Methods can return multiple values using lists
def getMinMax(numbers) {
[numbers.min(), numbers.max()]
}
def (min, max) = getMinMax([3, 7, 2, 9, 1])
println "Min: $min, Max: $max" // Min: 1, Max: 9
// Simple class
class Person {
String name
int age
// Method
String greet() {
"Hi, I'm $name and I'm $age years old"
}
}
// Create instance
def person = new Person(name: "Alice", age: 30)
println person.greet()
// Constructor
class Employee {
String name
String department
BigDecimal salary
Employee(String name, String department, BigDecimal salary) {
this.name = name
this.department = department
this.salary = salary
}
def givRaise(BigDecimal percentage) {
salary *= (1 + percentage / 100)
}
String toString() {
"$name - $department: \$${salary}"
}
}
def emp = new Employee("Bob", "Engineering", 75000)
emp.giveRaise(10)
println emp // Bob - Engineering: $82500
// Inheritance
class Manager extends Employee {
List<Employee> team = []
Manager(String name, String department, BigDecimal salary) {
super(name, department, salary)
}
def addTeamMember(Employee employee) {
team << employee
}
int getTeamSize() {
team.size()
}
}
// Traits (like interfaces but with implementation)
trait Flyable {
String fly() {
"I'm flying!"
}
}
trait Swimmable {
String swim() {
"I'm swimming!"
}
}
class Duck implements Flyable, Swimmable {
String name
}
def duck = new Duck(name: "Donald")
println duck.fly() // I'm flying!
println duck.swim() // I'm swimming!
One of Groovy's strengths is database integration. Here's a production-ready example using Groovy SQL:
@Grab('mysql:mysql-connector-java:8.0.33')
import groovy.sql.Sql
// Database connection
def db = Sql.newInstance(
'jdbc:mysql://localhost:3306/mydb',
'username',
'password',
'com.mysql.cj.jdbc.Driver'
)
try {
// Simple query
db.eachRow('SELECT * FROM users WHERE age > ?', [25]) { row ->
println "User: ${row.name}, Age: ${row.age}"
}
// Query with results
def users = db.rows('SELECT * FROM users ORDER BY name')
users.each { user ->
println "ID: ${user.id}, Name: ${user.name}"
}
// Single row
def user = db.firstRow('SELECT * FROM users WHERE id = ?', [1])
if (user) {
println "Found: ${user.name}"
}
// Insert
def inserted = db.executeInsert('''
INSERT INTO users (name, email, age, created_at)
VALUES (?, ?, ?, NOW())
''', ['Alice Smith', 'alice@example.com', 30])
println "Inserted ID: ${inserted[0][0]}"
// Update
def updated = db.executeUpdate('''
UPDATE users
SET age = ?
WHERE name = ?
''', [31, 'Alice Smith'])
println "Updated $updated rows"
// Delete
def deleted = db.execute('''
DELETE FROM users
WHERE email = ?
''', ['old@example.com'])
// Transaction
db.withTransaction {
db.executeInsert('INSERT INTO accounts (user_id, balance) VALUES (?, ?)', [1, 1000])
db.executeUpdate('UPDATE accounts SET balance = balance - ? WHERE user_id = ?', [100, 1])
}
// Batch insert
def batch = []
(1..100).each { i ->
batch << ["User$i", "user$i@example.com", 20 + i]
}
db.withBatch(10) { stmt ->
batch.each { row ->
stmt.addBatch('INSERT INTO users (name, email, age) VALUES (?, ?, ?)', row)
}
}
// Dynamic SQL with GStrings (use carefully!)
def tableName = 'users'
def column = 'name'
// Only safe when values come from trusted sources
def results = db.rows("SELECT * FROM $tableName WHERE $column LIKE ?", ['%Smith%'])
} finally {
db.close()
}
// Using connection pool for production
@Grab('com.zaxxer:HikariCP:5.0.1')
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
def config = new HikariConfig()
config.jdbcUrl = 'jdbc:mysql://localhost:3306/mydb'
config.username = 'username'
config.password = 'password'
config.maximumPoolSize = 10
config.connectionTimeout = 30000
def dataSource = new HikariDataSource(config)
def pooledDb = new Sql(dataSource)
// Use pooledDb for queries...
pooledDb.close()
dataSource.close()
Production Tips:
// Import specific class
import java.text.SimpleDateFormat
// Import all classes from package
import java.util.*
// Import with alias
import java.sql.Date as SqlDate
import java.util.Date as UtilDate
// Static imports
import static java.lang.Math.PI
import static java.lang.Math.sqrt
println PI // 3.141592653589793
println sqrt(16) // 4.0
// Groovy automatically imports common packages:
// - java.lang.*
// - java.util.*
// - java.io.*
// - java.net.*
// - groovy.lang.*
// - groovy.util.*
// Using Date without import (auto-imported)
def now = new Date()
println now
// Creating your own package
// File: com/example/Utils.groovy
package com.example
class Utils {
static String capitalize(String text) {
text ? text[0].toUpperCase() + text[1..-1] : ''
}
}
// Using it
import com.example.Utils
println Utils.capitalize("groovy") // Groovy
Groovy includes Grape (@Grab) for dependency management:
@Grab('org.apache.commons:commons-lang3:3.12.0')
import org.apache.commons.lang3.StringUtils
println StringUtils.capitalize("groovy rocks")
HTTP Clients:
@Grab('io.github.http-builder-ng:http-builder-ng-core:1.0.4')
import groovyx.net.http.HttpBuilderdef result = HttpBuilder.configure { request.uri = 'https://api.github.com/users/groovy' }.get() println result.name
**JSON Processing:**
- [JsonSlurper](https://docs.groovy-lang.org/latest/html/gapi/groovy/json/JsonSlurper.html) (built-in)
```groovy
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
def json = '{"name": "Alice", "age": 30}'
def parsed = new JsonSlurper().parseText(json)
println parsed.name // Alice
def output = JsonOutput.toJson([name: "Bob", age: 25])
println JsonOutput.prettyPrint(output)
XML Processing:
def xml = '''
<users>
<user id="1"><name>Alice</name></user>
<user id="2"><name>Bob</name></user>
</users>
'''def users = new XmlSlurper().parseText(xml) users.user.each { user -> println "${user.@id}: ${user.name}" }
**Testing:**
- [Spock Framework](http://spockframework.org/) - Expressive testing framework
- [JUnit](https://junit.org/) - Works seamlessly with Groovy
**Logging:**
- [SLF4J](http://www.slf4j.org/) with [Logback](http://logback.qos.ch/)
```groovy
@Grab('org.slf4j:slf4j-simple:2.0.7')
import org.slf4j.LoggerFactory
def log = LoggerFactory.getLogger(this.class)
log.info("Application started")
log.error("Something went wrong")
Database:
WaSQL provides robust support for Groovy, allowing you to leverage Groovy's expressive syntax and powerful features within your database-driven web applications. You can use Groovy in page controllers, functions, and standalone scripts alongside PHP, Python, and other supported languages.
Groovy's seamless Java integration means you can use any Java library within WaSQL, while its concise syntax makes database operations, JSON processing, and business logic more readable and maintainable. The combination of WaSQL's database-first architecture with Groovy's dynamic capabilities creates a powerful environment for rapid application development with enterprise-grade reliability.
<?groovy
recs=db.queryResults('sqlite_orders',"SELECT * FROM orders LIMIT 2")
println(recs)
?>
--------------Result------------------
[{"customer_id":12302,"ordernumber":8921,"order_amount":235.67,"state":"CA"},{"customer_id":32145,"ordernumber":3452,"order_amount":489.21,"state":"TX"}]
def for local variables unless you need explicit typing?.) to avoid NullPointerExceptionscollect over loops for transforming collections@Grab for dependencies in scripts@CompileStatic) for performance-critical code== for value equality (like Java's .equals()), use .is() for reference equality[] creates an ArrayList, [:] creates a HashMapthis: Closures have their own this context.toString() when needed/ with integers returns BigDecimal, use .intdiv() for integer divisionCongratulations on making it through this Groovy crash course! You now have a solid foundation. Here's what to explore next:
Remember, the best way to learn is by doing. Take your time, experiment with the examples, and don't be afraid to make mistakes. Groovy's friendly syntax and powerful features will make your coding journey enjoyable and productive.
Happy Groovy coding! 🚀