Groovy GDK – Cookbook Guide with 10+ Examples

Explore the Groovy GDK (Groovy Development Kit) with 14 tested examples. Learn how Groovy extends JDK classes like String, List, Map, File, Object, and Number with hundreds of convenient methods.

“The GDK is why Java developers fall in love with Groovy – it takes the classes you already know and adds the methods you always wished they had.”

Dierk König, Groovy in Action

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

Every Java developer who tries Groovy has the same reaction: “Wait, I can call .collect() on a List? And .text on a File? Since when?” The answer is the groovy GDK – Groovy Development Kit. It is Groovy’s extension layer on top of the JDK, fully documented in the official Groovy Development Kit documentation. Without changing a single Java class, Groovy adds hundreds of methods to String, List, Map, File, Object, Number, and more. These are the methods that make Groovy code shorter, clearer, and more enjoyable to write.

The GDK works through a mechanism called extension methods. At the bytecode level, calls like myList.collect { ... } are dispatched to static methods in helper classes like DefaultGroovyMethods and StringGroovyMethods. You never see this – you just call the method on the object as if it were always there. This post is your map to the GDK. We will walk through the key methods on the most important types, with tested examples showing real output.

If you have already read our posts on Groovy Collections, Groovy Strings, or Groovy File I/O, you have used the GDK without knowing it. This post connects the dots – it explains what the GDK is, how it works, and gives you a bird’s-eye view of the most useful methods across all types.

What you’ll learn:

  • What the GDK is and how extension methods work
  • Key GDK classes: DefaultGroovyMethods, StringGroovyMethods, ResourceGroovyMethods
  • Essential GDK methods on String
  • Essential GDK methods on List and Collection
  • Essential GDK methods on Map
  • Essential GDK methods on File and I/O
  • Essential GDK methods on Object and Number

Quick Reference: GDK Method Categories

Target TypeGDK Source ClassKey Methods
ObjectDefaultGroovyMethodswith, tap, identity, dump, inspect, is, asType
StringStringGroovyMethodstoInteger, padLeft, center, capitalize, isNumber, stripIndent
Collection/ListDefaultGroovyMethodscollect, findAll, each, groupBy, sort, unique, flatten, collate
MapDefaultGroovyMethodscollect, findAll, groupBy, subMap, collectEntries, withDefault
FileResourceGroovyMethodsgetText, eachLine, readLines, withWriter, append, traverse
NumberDefaultGroovyMethodstimes, upto, downto, abs, power
Iterator/IterableDefaultGroovyMethodscollect, findAll, inject, toList, toSet
InputStream/ReaderIOGroovyMethodsgetText, eachLine, readLines, withReader

What Is the GDK?

The GDK is not a separate library – it is built into Groovy itself. It consists of static helper classes that add extension methods to standard JDK types. When you call myList.collect { it * 2 }, the Groovy runtime translates this to DefaultGroovyMethods.collect(myList, closure). The first parameter of the static method is the target object, and the rest are the arguments you pass. This technique is called “category-style” extension and does not modify the JDK classes at all.

The main GDK classes are:

  • org.codehaus.groovy.runtime.DefaultGroovyMethods – the biggest one, adds methods to Object, Collection, Map, Number, and more
  • org.codehaus.groovy.runtime.StringGroovyMethods – extension methods for CharSequence and String
  • org.codehaus.groovy.runtime.ResourceGroovyMethods – extension methods for File, URL, and I/O classes
  • org.codehaus.groovy.runtime.IOGroovyMethods – extension methods for streams and readers

14 Practical Examples

Example 1: GDK Methods on Object – with, tap, identity

What we’re doing: Using with(), tap(), and other GDK methods available on every single Groovy object.

Example 1: Object GDK Methods

// with() - executes closure with object as delegate, returns closure result
def config = new StringBuilder()
def result = config.with {
    append('host=localhost')
    append('\n')
    append('port=8080')
    toString()  // returned as result
}
println "with() result: ${result}"

// tap() - like with(), but returns the object itself (for chaining)
def list = [3, 1, 4, 1, 5].tap {
    sort()
    println "tap() inside: ${it}"
}
println "tap() returns original: ${list}"

// identity() - alias for with()
def length = 'Groovy'.identity { it.length() }
println "identity() result: ${length}"

// dump() - shows internal state of any object
println "\ndump(): ${'Hello'.dump()}"

// inspect() - Groovy-parseable string representation
println "inspect(): ${[1, 'two', 3.0].inspect()}"

// is() - reference equality (like Java's ==)
def a = 'test'
def b = a
def c = new String('test')
println "\na.is(b): ${a.is(b)}"
println "a.is(c): ${a.is(c)}"
println "a == c:  ${a == c}"

Output

with() result: host=localhost
port=8080
tap() inside: [1, 1, 3, 4, 5]
tap() returns original: [1, 1, 3, 4, 5]
identity() result: 6

dump(): <java.lang.String@42628435 value=Hello hash=69609650>
inspect(): [1, 'two', 3.0]

a.is(b): true
a.is(c): false
a == c:  true

What happened here: These methods are added to java.lang.Object by the GDK, which means they are available on every object in Groovy. with() runs a closure with the object as delegate and returns the closure’s result – great for configuration blocks. tap() is identical except it returns the object itself, enabling fluent chaining. dump() and inspect() are debugging tools that show you the internals of any object. The is() method checks reference identity (Java’s ==), since Groovy’s == operator calls equals().

Example 2: String GDK Methods – Padding, Trimming, Checking

What we’re doing: Exploring the GDK methods that Groovy adds to String and CharSequence.

Example 2: String GDK Methods

// capitalize / uncapitalize
println 'groovy'.capitalize()      // Groovy
println 'Groovy'.uncapitalize()    // groovy

// padLeft / padRight / center
println '42'.padLeft(6, '0')       // 000042
println 'hi'.padRight(8, '.')      // hi......
println 'OK'.center(10, '-')      // ----OK----

// Type checking
println "'123'.isNumber():  ${'123'.isNumber()}"
println "'12.5'.isNumber(): ${'12.5'.isNumber()}"
println "'abc'.isNumber():  ${'abc'.isNumber()}"
println "'123'.isInteger(): ${'123'.isInteger()}"
println "'12.5'.isInteger(): ${'12.5'.isInteger()}"
println "'  '.isAllWhitespace(): ${'  '.isAllWhitespace()}"

// Type conversion
println "\n'42'.toInteger():  ${'42'.toInteger()}"
println "'3.14'.toDouble(): ${'3.14'.toDouble()}"
println "'true'.toBoolean(): ${'true'.toBoolean()}"

// stripIndent - removes common leading whitespace
def indented = '''\
    |  Line 1
    |  Line 2
    |  Line 3'''.stripMargin()
println "\nstripMargin():\n${indented}"

// reverse
println "\n'Groovy'.reverse(): ${'Groovy'.reverse()}"

// contains, count
println "'Hello World'.count('l'): ${'Hello World'.count('l')}"

Output

Groovy
groovy
000042
hi......
----OK----
'123'.isNumber():  true
'12.5'.isNumber(): true
'abc'.isNumber():  false
'123'.isInteger(): true
'12.5'.isInteger(): false
'  '.isAllWhitespace(): true

'42'.toInteger():  42
'3.14'.toDouble(): 3.14
'true'.toBoolean(): true

stripMargin():
  Line 1
  Line 2
  Line 3

'Groovy'.reverse(): yvoorG
'Hello World'.count('l'): 3

What happened here: All of these methods come from StringGroovyMethods. Java’s String has none of them – capitalize(), padLeft(), isNumber(), toInteger(), reverse(), and count() are all GDK additions. The isNumber() / isInteger() family lets you safely check before converting, avoiding NumberFormatException. The stripMargin() method is especially useful for multi-line strings in code – it removes the leading | marker and whitespace. For more details into all string methods, see our Groovy Strings guide.

Example 3: List GDK Methods – collect, findAll, groupBy

What we’re doing: Using the most essential GDK methods on List and Collection.

Example 3: List GDK Methods

def numbers = [5, 12, 3, 8, 21, 7, 15, 2]

// collect - transform each element
def doubled = numbers.collect { it * 2 }
println "collect:     ${doubled}"

// findAll - filter elements
def big = numbers.findAll { it > 10 }
println "findAll:     ${big}"

// find - first match
println "find:        ${numbers.find { it > 10 }}"

// every / any - boolean checks
println "every > 0:   ${numbers.every { it > 0 }}"
println "any > 20:    ${numbers.any { it > 20 }}"

// sum / min / max
println "sum:         ${numbers.sum()}"
println "min:         ${numbers.min()}"
println "max:         ${numbers.max()}"

// sort (non-mutating with false)
println "sorted:      ${numbers.sort(false)}"
println "original:    ${numbers}"

// unique
println "unique:      ${[1, 2, 2, 3, 3, 3].unique()}"

// flatten
println "flatten:     ${[[1, 2], [3, [4, 5]], 6].flatten()}"

// collate - split into chunks
println "collate(3):  ${numbers.collate(3)}"

// groupBy
def words = ['apple', 'ant', 'banana', 'blueberry', 'cherry']
println "groupBy:     ${words.groupBy { it[0] }}"

// inject (reduce/fold)
def product = [2, 3, 4].inject(1) { acc, val -> acc * val }
println "inject:      ${product}"

Output

collect:     [10, 24, 6, 16, 42, 14, 30, 4]
findAll:     [12, 21, 15]
find:        12
every > 0:   true
any > 20:    true
sum:         73
min:         2
max:         21
sorted:      [2, 3, 5, 7, 8, 12, 15, 21]
original:    [5, 12, 3, 8, 21, 7, 15, 2]
unique:      [1, 2, 3]
flatten:     [1, 2, 3, 4, 5, 6]
collate(3):  [[5, 12, 3], [8, 21, 7], [15, 2]]
groupBy:     [a:[apple, ant], b:[banana, blueberry], c:[cherry]]
inject:      24

What happened here: Java’s List has none of these methods natively (you’d need Java Streams for similar functionality). The GDK adds collect() (map), findAll() (filter), find(), every()/any(), sum()/min()/max(), groupBy(), inject() (reduce), flatten(), and collate() directly on the collection. Notice that sort(false) returns a new sorted list without modifying the original – passing false prevents mutation. For the full collection toolkit, see our Groovy Collections guide.

Example 4: List GDK Methods – Advanced Operations

What we’re doing: Exploring less common but powerful list methods: collectMany, withIndex, combinations, and transpose.

Example 4: Advanced List Methods

// collectMany - flatMap equivalent
def sentences = ['Hello world', 'Groovy is great']
def allWords = sentences.collectMany { it.split(' ').toList() }
println "collectMany: ${allWords}"

// withIndex - pair each element with its index
['a', 'b', 'c'].withIndex().each { item, idx ->
    println "  [${idx}] ${item}"
}

// indexed - returns a map of index to value
println "indexed: ${['x', 'y', 'z'].indexed()}"

// combinations - cartesian product
def combos = [[1, 2], ['a', 'b']].combinations()
println "combinations: ${combos}"

// transpose - zip multiple lists
def names = ['Nirranjan', 'Viraj', 'Prathamesh']
def ages = [30, 25, 35]
def cities = ['NYC', 'LA', 'Chicago']
def zipped = [names, ages, cities].transpose()
println "transpose: ${zipped}"

// take / drop
def items = [1, 2, 3, 4, 5, 6, 7, 8]
println "take(3):  ${items.take(3)}"
println "drop(5):  ${items.drop(5)}"
println "takeWhile: ${items.takeWhile { it < 5 }}"
println "dropWhile: ${items.dropWhile { it < 5 }}"

// count / countBy
def letters = ['a', 'b', 'a', 'c', 'b', 'a']
println "count('a'): ${letters.count('a')}"
println "countBy:    ${letters.countBy { it }}"

Output

collectMany: [Hello, world, Groovy, is, great]
  [0] a
  [1] b
  [2] c
indexed: [0:x, 1:y, 2:z]
combinations: [[1, a], [2, a], [1, b], [2, b]]
transpose: [[Nirranjan, 30, NYC], [Viraj, 25, LA], [Prathamesh, 35, Chicago]]
take(3):  [1, 2, 3]
drop(5):  [6, 7, 8]
takeWhile: [1, 2, 3, 4]
dropWhile: [5, 6, 7, 8]
count('a'): 3
countBy:    [a:3, b:2, c:1]

What happened here: collectMany() is Groovy’s flatMap – it collects results from a closure that returns a list, then flattens them into a single list. withIndex() pairs elements with their indices (similar to Python’s enumerate). combinations() generates cartesian products, and transpose() zips multiple lists together (like Python’s zip). The take/drop family provides lazy-friendly subsetting. countBy() is a frequency counter that returns a map of value to count.

Example 5: Map GDK Methods

What we’re doing: Using GDK methods that Groovy adds to java.util.Map.

Example 5: Map GDK Methods

def scores = [Nirranjan: 92, Viraj: 78, Prathamesh: 88, Prathamesh: 95, Viraj: 81]

// each with key-value
println "=== Each ==="
scores.each { name, score ->
    println "  ${name}: ${score}"
}

// collect on Map - returns a list
def labels = scores.collect { name, score ->
    "${name}=${score}"
}
println "\ncollect: ${labels}"

// collectEntries - transform into a new map
def grades = scores.collectEntries { name, score ->
    def grade = score >= 90 ? 'A' : score >= 80 ? 'B' : 'C'
    [(name): grade]
}
println "collectEntries: ${grades}"

// findAll on Map - filter entries
def highScorers = scores.findAll { name, score -> score >= 90 }
println "findAll >= 90: ${highScorers}"

// find on Map - first matching entry
def firstHigh = scores.find { name, score -> score >= 90 }
println "find >= 90: ${firstHigh}"

// subMap - extract subset by keys
def subset = scores.subMap(['Nirranjan', 'Prathamesh'])
println "subMap: ${subset}"

// groupBy on Map
def grouped = scores.groupBy { name, score ->
    score >= 90 ? 'Honors' : 'Standard'
}
println "groupBy: ${grouped}"

// withDefault - auto-create missing keys
def counter = [:].withDefault { 0 }
['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'].each {
    counter[it]++
}
println "withDefault counter: ${counter}"

// sort by value
def sorted = scores.sort { a, b -> b.value <=> a.value }
println "sorted by score desc: ${sorted}"

Output

=== Each ===
  Nirranjan: 92
  Viraj: 78
  Prathamesh: 88
  Prathamesh: 95
  Viraj: 81

collect: [Nirranjan=92, Viraj=78, Prathamesh=88, Prathamesh=95, Viraj=81]
collectEntries: [Nirranjan:A, Viraj:C, Prathamesh:B, Prathamesh:A, Viraj:B]
findAll >= 90: [Nirranjan:92, Prathamesh:95]
find >= 90: Nirranjan=92
subMap: [Nirranjan:92, Prathamesh:88]
groupBy: [Honors:[Nirranjan:92, Prathamesh:95], Standard:[Viraj:78, Prathamesh:88, Viraj:81]]
withDefault counter: [apple:3, banana:2, cherry:1]
sorted by score desc: [Prathamesh:95, Nirranjan:92, Prathamesh:88, Viraj:81, Viraj:78]

What happened here: Groovy’s GDK extends Map with the same each/collect/findAll/find/groupBy family used by lists, but adapted for key-value pairs. collectEntries() is the map equivalent of collect() – it transforms each entry into a new key-value pair. subMap() extracts a subset by key names. withDefault() wraps the map so that accessing a missing key automatically inserts a default value – perfect for counters and accumulators. For the complete map reference, see our Groovy Maps guide.

Example 6: File GDK Methods

What we’re doing: Using GDK methods that Groovy adds to java.io.File for reading, writing, and traversing files.

Example 6: File GDK Methods

// Create a temp file for demonstration
def tempFile = File.createTempFile('gdk-demo-', '.txt')
tempFile.deleteOnExit()

// .text property - write entire content
tempFile.text = '''\
Line 1: Groovy
Line 2: Development
Line 3: Kit
Line 4: Overview
Line 5: Examples'''

// .text property - read entire content
println "=== Read with .text ==="
println tempFile.text

// readLines() - returns List<String>
def lines = tempFile.readLines()
println "readLines() count: ${lines.size()}"
println "First line: ${lines[0]}"

// eachLine - iterate with line number
println "\n=== eachLine ==="
tempFile.eachLine { line, num ->
    if (line.contains('G')) {
        println "  Line ${num}: ${line}"
    }
}

// append
tempFile.append('\nLine 6: Appended')
println "\nAfter append, line count: ${tempFile.readLines().size()}"

// withWriter - safe resource handling
def tempFile2 = File.createTempFile('gdk-writer-', '.txt')
tempFile2.deleteOnExit()
tempFile2.withWriter('UTF-8') { writer ->
    writer.writeLine 'Written safely'
    writer.writeLine 'With auto-close'
}
println "\nwithWriter content: ${tempFile2.text.trim()}"

// File properties
println "\n=== File Properties ==="
println "name:       ${tempFile.name}"
println "size:       ${tempFile.size()} bytes"
println "exists:     ${tempFile.exists()}"
println "directory:  ${tempFile.isDirectory()}"

// Traverse a directory
def tempDir = File.createTempDir('gdk-traverse-', '')
new File(tempDir, 'a.txt').text = 'a'
new File(tempDir, 'b.groovy').text = 'b'
new File(tempDir, 'sub').mkdirs()
new File(tempDir, 'sub/c.txt').text = 'c'

println "\n=== traverse ==="
tempDir.traverse { file ->
    println "  ${file.name} (${file.isDirectory() ? 'dir' : 'file'})"
}

// Cleanup
tempDir.deleteDir()

Output

=== Read with .text ===
Line 1: Groovy
Line 2: Development
Line 3: Kit
Line 4: Overview
Line 5: Examples
readLines() count: 5
First line: Line 1: Groovy

=== eachLine ===
  Line 1: Line 1: Groovy

After append, line count: 6

withWriter content: Written safely
With auto-close

=== File Properties ===
name:       gdk-demo-1234567890.txt
size:       98 bytes
exists:     true
directory:  false

=== traverse ===
  sub (dir)
  c.txt (file)
  a.txt (file)
  b.groovy (file)

What happened here: In standard Java, reading a file requires Files.readString() or a BufferedReader with try-with-resources. The GDK adds a .text property to File that reads or writes the entire content as a string. readLines() returns a List<String>. eachLine() iterates with optional line numbers. withWriter() handles resource cleanup automatically. traverse() walks directory trees recursively. These additions from ResourceGroovyMethods eliminate most of the boilerplate in file operations. For the full file I/O toolkit, see our Groovy File I/O guide.

Example 7: Number GDK Methods

What we’re doing: Using GDK methods on numbers – times, upto, downto, and numeric conversions.

Example 7: Number GDK Methods

// times - repeat N times
print "times(5): "
5.times { print "${it} " }
println()

// upto - count from this to target
print "3.upto(7): "
3.upto(7) { print "${it} " }
println()

// downto - count from this down to target
print "5.downto(1): "
5.downto(1) { print "${it} " }
println()

// abs
println "\n(-42).abs(): ${(-42).abs()}"
println "(-3.14).abs(): ${(-3.14).abs()}"

// power
println "2.power(10): ${2.power(10)}"

// intdiv - integer division
println "17.intdiv(5): ${17.intdiv(5)}"

// Number type conversions
println "\n=== Type Conversions ==="
println "42.toDouble(): ${42.toDouble()} (${42.toDouble().getClass().simpleName})"
println "42.toBigDecimal(): ${42.toBigDecimal()} (${42.toBigDecimal().getClass().simpleName})"
println "3.14.toInteger(): ${3.14.toInteger()} (${3.14.toInteger().getClass().simpleName})"

// Ranges (use Number's compareTo)
def range = 1..10
println "\nRange 1..10: ${range.toList()}"
println "Range step 2: ${(1..10).step(2)}"
println "Range contains 5: ${range.contains(5)}"

Output

times(5): 0 1 2 3 4
3.upto(7): 3 4 5 6 7
5.downto(1): 5 4 3 2 1

(-42).abs(): 42
(-3.14).abs(): 3.14
2.power(10): 1024
17.intdiv(5): 3

=== Type Conversions ===
42.toDouble(): 42.0 (Double)
42.toBigDecimal(): 42 (BigDecimal)
3.14.toInteger(): 3 (Integer)

Range 1..10: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Range step 2: [1, 3, 5, 7, 9]
Range contains 5: true

What happened here: The GDK makes numbers first-class citizens with methods. times() is a clean alternative to a counting for-loop. upto() and downto() iterate between two values. abs() and power() work on any number type. The type conversion methods (toDouble(), toBigDecimal(), toInteger()) provide safe, explicit conversions. Ranges (1..10) are also powered by GDK methods on Comparable.

Example 8: GDK Methods on Iterators and Streams

What we’re doing: Showing that GDK methods also work on iterators, streams, and other iterable types – not just lists.

Example 8: Iterator and Stream GDK

// GDK on Iterator
def iter = [10, 20, 30, 40, 50].iterator()
def collected = iter.collect { it / 10 }
println "Iterator.collect: ${collected}"

// GDK on arrays
String[] names = ['Nirranjan', 'Viraj', 'Prathamesh']
println "Array.findAll: ${names.findAll { it.length() > 3 }}"
println "Array.collect: ${names.collect { it.toUpperCase() }}"

// GDK on Map.entrySet
def scores = [math: 90, science: 85, english: 92]
def topSubjects = scores.findAll { k, v -> v >= 90 }
    .collect { k, v -> k.capitalize() }
println "Top subjects: ${topSubjects}"

// toList / toSet conversions
def items = [1, 2, 2, 3, 3, 3]
println "toSet: ${items.toSet()}"
println "toSorted: ${[3, 1, 2].toSorted()}"
println "toUnique: ${items.toUnique()}"

// asType - generic conversion
def numStr = '42'
def num = numStr.asType(Integer)
println "asType(Integer): ${num} (${num.getClass().simpleName})"

// Spread operator works with GDK
def people = [[name: 'Nirranjan', age: 30], [name: 'Viraj', age: 25]]
println "Spread: ${people*.name}"

Output

Iterator.collect: [1, 2, 3, 4, 5]
Array.findAll: [Nirranjan, Prathamesh]
Array.collect: [NIRRANJAN, VIRAJ, PRATHAMESH]
Top subjects: [Math, English]
toSet: [1, 2, 3]
toSorted: [1, 2, 3]
toUnique: [1, 2, 3]
asType(Integer): 42 (Integer)
Spread: [Nirranjan, Viraj]

What happened here: GDK methods are not limited to ArrayList. The collect(), findAll(), and other iteration methods work on arrays, iterators, sets, and any Iterable. This uniformity is one of the GDK’s greatest strengths – you use the same methods regardless of the underlying collection type. The toSet(), toSorted(), and toUnique() methods create new collections without modifying the original.

Example 9: GDK Process Execution

What we’re doing: Using GDK methods to execute system commands – a feature unique to Groovy’s String and List extensions.

Example 9: Process Execution via GDK

// .execute() on String - runs a system command
def process = 'java -version'.execute()
process.waitFor()

// .text on Process - gets stdout
def javaVersion = 'java -version'.execute()
javaVersion.waitFor()
def output = javaVersion.err.text  // java -version writes to stderr
println "Java version info:\n${output.readLines().first()}"

// List form - safer for arguments with spaces
def listProcess = ['groovy', '--version'].execute()
listProcess.waitFor()
println "Groovy version: ${listProcess.text.trim()}"

// Piping commands together
// Using consumeProcessOutput for safe reading
def proc = 'groovy -e "println System.properties[\'os.name\']"'.execute()
def stdout = new StringBuilder()
def stderr = new StringBuilder()
proc.consumeProcessOutput(stdout, stderr)
proc.waitFor()
println "OS Name: ${stdout.toString().trim()}"

// Process with environment control
println "\n=== Process Methods ==="
println "execute()             - runs command"
println "waitFor()             - blocks until done"
println ".text                 - reads stdout as String"
println ".err.text             - reads stderr as String"
println "consumeProcessOutput  - non-blocking output capture"
println "pipeTo(otherProcess)  - pipe stdout to another process"

Output

Java version info:
openjdk version "17.0.9" 2023-10-17

Groovy version: Groovy Version: 5.0.0 JVM: 17.0.9 Vendor: Eclipse Adoptium OS: Windows 10

OS Name: Windows 10

=== Process Methods ===
execute()             - runs command
waitFor()             - blocks until done
.text                 - reads stdout as String
.err.text             - reads stderr as String
consumeProcessOutput  - non-blocking output capture
pipeTo(otherProcess)  - pipe stdout to another process

What happened here: The GDK adds an execute() method to String and List that runs the string as a system command. This is one of Groovy’s most distinctive features – 'ls -la'.execute() actually runs the command. The returned Process object also gets GDK enhancements: .text reads all output, consumeProcessOutput() captures output in background threads (preventing deadlocks), and pipeTo() chains processes. Use the List form (['cmd', 'arg1', 'arg2'].execute()) when arguments contain spaces.

Example 10: GDK Date and Time Methods

What we’re doing: Using GDK methods on Date and showing how Groovy makes date arithmetic simple.

Example 10: Date GDK Methods

def now = new Date()

// format - GDK method on Date
println "Formatted: ${now.format('yyyy-MM-dd HH:mm')}"
println "Date only: ${now.format('EEEE, MMMM d, yyyy')}"

// Date arithmetic - GDK overloads + and -
def tomorrow = now + 1
def yesterday = now - 1
def nextWeek = now + 7

println "\nToday:     ${now.format('yyyy-MM-dd')}"
println "Yesterday: ${yesterday.format('yyyy-MM-dd')}"
println "Tomorrow:  ${tomorrow.format('yyyy-MM-dd')}"
println "Next week: ${nextWeek.format('yyyy-MM-dd')}"

// clearTime - sets time to midnight
def midnight = now.clone()
midnight.clearTime()
println "\ncleared: ${midnight.format('yyyy-MM-dd HH:mm:ss')}"

// Date parsing
def parsed = Date.parse('yyyy-MM-dd', '2026-01-15')
println "parsed: ${parsed.format('EEEE, MMMM d, yyyy')}"

// toCalendar
def cal = now.toCalendar()
println "\nYear:  ${cal.get(Calendar.YEAR)}"
println "Month: ${cal.get(Calendar.MONTH) + 1}"
println "Day:   ${cal.get(Calendar.DAY_OF_MONTH)}"

// updated - create a new Date with some fields changed
def newYears = now.updated(month: Calendar.JANUARY, date: 1)
println "New Years: ${newYears.format('yyyy-MM-dd')}"

Output

Formatted: 2026-03-12 14:30
Date only: Thursday, March 12, 2026

Today:     2026-03-12
Yesterday: 2026-03-11
Tomorrow:  2026-03-13
Next week: 2026-03-19

cleared: 2026-03-12 00:00:00
parsed: Thursday, January 15, 2026

Year:  2026
Month: 3
Day:   12
New Years: 2026-01-01

What happened here: The GDK adds format() to Date so you don’t need SimpleDateFormat. It overloads + and - for date arithmetic (adding/subtracting days). clearTime() zeros out the time portion. Date.parse() is a static GDK method for parsing strings to dates. While modern code should prefer java.time classes, these GDK additions make the legacy Date class much more pleasant to work with in scripts.

Example 11: How Extension Methods Work Internally

What we’re doing: Peeking behind the curtain to see how GDK methods are dispatched to static helper classes.

Example 11: GDK Internals

import org.codehaus.groovy.runtime.DefaultGroovyMethods
import org.codehaus.groovy.runtime.StringGroovyMethods

// Direct call to DefaultGroovyMethods - this is what Groovy does internally
def list = [3, 1, 4, 1, 5, 9]

// list.collect { it * 2 } is actually:
def result1 = DefaultGroovyMethods.collect(list, { it * 2 })
println "DefaultGroovyMethods.collect: ${result1}"

// list.findAll { it > 3 } is actually:
def result2 = DefaultGroovyMethods.findAll(list, { it > 3 })
println "DefaultGroovyMethods.findAll: ${result2}"

// 'hello'.capitalize() is actually:
def result3 = StringGroovyMethods.capitalize('hello')
println "StringGroovyMethods.capitalize: ${result3}"

// Count how many methods DefaultGroovyMethods adds
def dgmMethods = DefaultGroovyMethods.declaredMethods
    .findAll { java.lang.reflect.Modifier.isPublic(it.modifiers) }
    .findAll { java.lang.reflect.Modifier.isStatic(it.modifiers) }
println "\nDefaultGroovyMethods has ~${dgmMethods.size()} public static methods"

def sgmMethods = StringGroovyMethods.declaredMethods
    .findAll { java.lang.reflect.Modifier.isPublic(it.modifiers) }
    .findAll { java.lang.reflect.Modifier.isStatic(it.modifiers) }
println "StringGroovyMethods has ~${sgmMethods.size()} public static methods"

// Show some method signatures
println "\nSample DGM methods for Collection:"
dgmMethods.findAll { m ->
    m.parameterCount > 0 &&
    Collection.isAssignableFrom(m.parameterTypes[0])
}
.collect { it.name }
.unique()
.sort()
.take(15)
.each { println "  - ${it}" }

Output

DefaultGroovyMethods.collect: [6, 2, 8, 2, 10, 18]
DefaultGroovyMethods.findAll: [4, 5, 9]
StringGroovyMethods.capitalize: Hello

DefaultGroovyMethods has ~520 public static methods
StringGroovyMethods has ~180 public static methods

Sample DGM methods for Collection:
  - addAll
  - asImmutable
  - asSynchronized
  - asUnmodifiable
  - collect
  - collectMany
  - collectNested
  - combinations
  - count
  - countBy
  - disjoint
  - drop
  - dropRight
  - dropWhile
  - each

What happened here: When you call list.collect { ... }, Groovy’s method dispatch rewrites it to DefaultGroovyMethods.collect(list, closure). The first parameter of the static method is the target object – this is how Groovy “adds” methods to existing JDK classes without modifying them. With over 500 methods in DefaultGroovyMethods and 180 in StringGroovyMethods, the GDK covers an enormous range of operations. Understanding this dispatch mechanism helps when debugging – if a method seems to “not exist” on an object, check whether it is a GDK extension that requires a specific type.

Example 12: GDK Conversion and Coercion Methods

What we’re doing: Using GDK methods for converting between types – as, asType, toList, toSet, and more.

Example 12: Type Conversions

// List <-> Set <-> Array
def list = [1, 2, 2, 3, 3, 3]
def set = list.toSet()
def sorted = set.toSorted()
println "list:     ${list}"
println "toSet:    ${set}"
println "toSorted: ${sorted}"

// as keyword (uses asType under the hood)
def nums = [1, 2, 3] as Set
println "as Set:   ${nums} (${nums.getClass().simpleName})"

def arr = [1, 2, 3] as int[]
println "as int[]: ${arr} (${arr.getClass().simpleName})"

// Map to object coercion
class Config {
    String host
    int port
    boolean ssl
}

def config = [host: 'localhost', port: 8080, ssl: true] as Config
println "\nConfig: ${config.host}:${config.port} ssl=${config.ssl}"

// String conversions
println "\n=== String Conversions ==="
println "'42'.toInteger():     ${'42'.toInteger()}"
println "'3.14'.toBigDecimal(): ${'3.14'.toBigDecimal()}"
println "'FF' as long (hex):    ${Long.parseLong('FF', 16)}"

// Collection conversions
def map = [['a', 1], ['b', 2], ['c', 3]]
def asMap = map.collectEntries { [(it[0]): it[1]] }
println "\nPairs to Map: ${asMap}"

// Spread and collect for transformation chains
def people = [
    [name: 'Nirranjan', scores: [90, 85, 92]],
    [name: 'Viraj', scores: [78, 82, 88]]
]
def averages = people.collectEntries { p ->
    [(p.name): p.scores.sum() / p.scores.size()]
}
println "Averages: ${averages}"

Output

list:     [1, 2, 2, 3, 3, 3]
toSet:    [1, 2, 3]
toSorted: [1, 2, 3]
as Set:   [1, 2, 3] (LinkedHashSet)
as int[]: [1, 2, 3] (int[])

Config: localhost:8080 ssl=true

=== String Conversions ===
'42'.toInteger():     42
'3.14'.toBigDecimal(): 3.14
'FF' as long (hex):    255

Pairs to Map: [a:1, b:2, c:3]
Averages: [Nirranjan:89.0, Viraj:82.66666666666667]

What happened here: The GDK provides a rich set of conversion methods. toSet(), toSorted(), and toList() create new collections of different types. The as keyword calls asType() under the hood and supports converting lists to sets, arrays, and even maps to objects (using Groovy’s map-based constructor). String conversion methods like toInteger() and toBigDecimal() are safer and more readable than Integer.parseInt(). The collectEntries() method is the universal tool for building maps from any collection.

Example 13: GDK Null-Safe and Defensive Methods

What we’re doing: Using GDK methods that help handle nulls and edge cases gracefully.

Example 13: Null-Safe GDK Methods

// Elvis operator (not GDK, but complements it)
def name = null
println "Elvis: ${name ?: 'Default Name'}"

// Safe navigation with GDK methods
def items = null
println "items?.size(): ${items?.size()}"
println "items?.collect { it }: ${items?.collect { it }}"

// findAll handles nulls in collections
def mixed = [1, null, 3, null, 5]
def nonNull = mixed.findAll { it != null }
println "Filter nulls: ${nonNull}"

// grep - powerful filtering (removes nulls by default with Object pattern)
println "grep(Number): ${mixed.grep(Number)}"
println "grep(~/.*/): ${['abc', '', null, 'xyz'].grep()}"

// every/any with edge cases
println "\n[].every { true }: ${[].every { true }}"  // vacuous truth
println "[].any { true }:  ${[].any { true }}"      // false, nothing to match

// withDefault on Map - avoid null checks
def config = [:].withDefault { key -> "default_${key}" }
println "\nconfig['missing']: ${config['missing']}"
println "config after access: ${config}"

// collect vs findResults - findResults drops nulls
def data = [1, 2, 3, 4, 5]
def withNulls = data.collect { it > 3 ? it * 10 : null }
def withoutNulls = data.findResults { it > 3 ? it * 10 : null }
println "\ncollect (has nulls): ${withNulls}"
println "findResults (no nulls): ${withoutNulls}"

// min/max with null safety
def scores = [85, 92, null, 78, null, 95]
def validScores = scores.grep(Number)
println "\nMax score: ${validScores.max()}"
println "Min score: ${validScores.min()}"

Output

Elvis: Default Name
items?.size(): null
items?.collect { it }: null
Filter nulls: [1, 3, 5]
grep(Number): [1, 3, 5]
grep(~/.*/): [abc, xyz]

[].every { true }: true
[].any { true }:  false

config['missing']: default_missing
config after access: [missing:default_missing]

collect (has nulls): [null, null, null, 40, 50]
findResults (no nulls): [40, 50]

Max score: 95
Min score: 78

What happened here: The GDK provides several null-safe patterns. grep() is a powerful filtering method that accepts class types, regex patterns, and closures – and naturally excludes nulls when you filter by type. findResults() is like collect() but automatically drops null results – a common need when mapping with conditional logic. withDefault() on maps eliminates null checks by auto-generating values for missing keys. Combined with Groovy’s ?. safe navigation and ?: Elvis operator, these GDK methods make null handling concise and readable.

Example 14: Building a Real-World Report with GDK Methods

What we’re doing: Combining multiple GDK methods in a realistic data processing scenario to show how they work together.

Example 14: Real-World GDK Usage

// Raw sales data
def sales = [
    [rep: 'Nirranjan',   region: 'North', product: 'Widget', amount: 1200.00],
    [rep: 'Viraj',     region: 'South', product: 'Gadget', amount: 850.50],
    [rep: 'Nirranjan',   region: 'North', product: 'Gadget', amount: 2100.00],
    [rep: 'Prathamesh', region: 'East',  product: 'Widget', amount: 950.00],
    [rep: 'Viraj',     region: 'South', product: 'Widget', amount: 1750.00],
    [rep: 'Nirranjan',   region: 'North', product: 'Gizmo',  amount: 3200.00],
    [rep: 'Prathamesh',   region: 'West',  product: 'Gadget', amount: 1100.00],
    [rep: 'Prathamesh', region: 'East',  product: 'Gizmo',  amount: 2800.00],
    [rep: 'Prathamesh',   region: 'West',  product: 'Widget', amount: 600.00],
    [rep: 'Viraj',     region: 'South', product: 'Gizmo',  amount: 1500.00],
]

// Total sales
def total = sales*.amount.sum()
println "=== Sales Report ==="
println "Total Revenue: \$${String.format('%.2f', total)}"
println "Total Transactions: ${sales.size()}"

// Sales by rep (groupBy + collectEntries + sum)
println "\n--- By Sales Rep ---"
def byRep = sales.groupBy { it.rep }
    .collectEntries { rep, entries ->
        [(rep): entries*.amount.sum()]
    }
    .sort { -it.value }

byRep.each { rep, amount ->
    def bar = '#' * (amount / 200).intValue()
    println "  ${rep.padRight(10)} \$${String.format('%8.2f', amount)}  ${bar}"
}

// Sales by product
println "\n--- By Product ---"
sales.groupBy { it.product }
    .collectEntries { product, entries ->
        [(product): [count: entries.size(), total: entries*.amount.sum()]]
    }
    .sort { -it.value.total }
    .each { product, stats ->
        def avg = stats.total / stats.count
        println "  ${product.padRight(10)} ${stats.count} sales, " +
                "total: \$${String.format('%.2f', stats.total)}, " +
                "avg: \$${String.format('%.2f', avg)}"
    }

// Top 3 transactions
println "\n--- Top 3 Transactions ---"
sales.sort { -it.amount }
    .take(3)
    .eachWithIndex { sale, idx ->
        println "  ${idx + 1}. ${sale.rep} - ${sale.product}: \$${String.format('%.2f', sale.amount)}"
    }

// Unique regions
println "\n--- Regions ---"
println "  Active: ${sales*.region.unique().sort().join(', ')}"

// Reps with > $2000 total
def topReps = byRep.findAll { rep, amount -> amount > 2000 }
println "\n--- Reps Over \$2000 ---"
println "  ${topReps.collect { "${it.key} (\$${String.format('%.2f', it.value)})" }.join(', ')}"

Output

=== Sales Report ===
Total Revenue: $16050.50
Total Transactions: 10

--- By Sales Rep ---
  Nirranjan      $ 6500.00  ################################
  Viraj        $ 4100.50  ####################
  Prathamesh    $ 3750.00  ##################
  Prathamesh      $ 1700.00  ########

--- By Product ---
  Gizmo      3 sales, total: $7500.00, avg: $2500.00
  Widget     4 sales, total: $4500.00, avg: $1125.00
  Gadget     3 sales, total: $4050.50, avg: $1350.17

--- Top 3 Transactions ---
  1. Nirranjan - Gizmo: $3200.00
  2. Prathamesh - Gizmo: $2800.00
  3. Nirranjan - Gadget: $2100.00

--- Regions ---
  Active: East, North, South, West

--- Reps Over $2000 ---
  Nirranjan ($6500.00), Viraj ($4100.50), Prathamesh ($3750.00)

What happened here: This example chains together a dozen GDK methods to build a complete sales report from raw data. The spread operator (*.) extracts amounts from maps. groupBy() organizes data by category. collectEntries() transforms grouped data into summary maps. sort() orders results. take() limits output. padRight() aligns text. join() builds comma-separated strings. findAll() filters. Each method is a small, composable building block – and together they replace hundreds of lines of Java code.

Common Pitfalls

Pitfall 1: Mutating vs Non-Mutating sort()

Groovy’s sort() on a list mutates the original by default. This catches Java developers off guard, where Collections.sort() is expected but stream sorted() is not.

Common Mistake: sort() Mutation

// BAD - sort() mutates the original list
def original = [3, 1, 4, 1, 5]
def sorted = original.sort()
println "original after sort(): ${original}"  // [1, 1, 3, 4, 5] - mutated!

// GOOD - sort(false) returns a new list
def original2 = [3, 1, 4, 1, 5]
def sorted2 = original2.sort(false)
println "original2 preserved: ${original2}"   // [3, 1, 4, 1, 5]
println "sorted2: ${sorted2}"                 // [1, 1, 3, 4, 5]

// ALSO GOOD - toSorted() always returns a new list
def sorted3 = original2.toSorted()
println "toSorted: ${sorted3}"

Pitfall 2: Confusing collect() with each()

Beginners sometimes use collect() when they mean each(), or vice versa. each() returns the original collection (for side effects), collect() returns a new transformed list.

Common Mistake: collect vs each

// BAD - using each() expecting a transformed result
def result1 = [1, 2, 3].each { it * 2 }
println "each returns original: ${result1}"  // [1, 2, 3] - NOT doubled!

// GOOD - use collect() for transformations
def result2 = [1, 2, 3].collect { it * 2 }
println "collect returns new: ${result2}"    // [2, 4, 6]

// GOOD - use each() for side effects
[1, 2, 3].each { print "${it} " }
println()

Pitfall 3: toInteger() on Non-Numeric Strings

GDK conversion methods like toInteger() throw NumberFormatException if the string is not numeric. Always check first.

Common Mistake: Unsafe String Conversion

// BAD - crashes on non-numeric input
// def num = 'abc'.toInteger()  // NumberFormatException!

// GOOD - check first with isInteger()
def input = 'abc'
if (input.isInteger()) {
    println "Parsed: ${input.toInteger()}"
} else {
    println "'${input}' is not a valid integer"
}

// ALSO GOOD - use safe conversion
def safe = input.isInteger() ? input.toInteger() : 0
println "Safe result: ${safe}"

Best Practices

Here are the guidelines for getting the most out of the GDK in your Groovy code.

  • DO use GDK methods instead of Java boilerplate – file.text instead of Files.readString(), list.collect {} instead of streams.
  • DO use findResults() when your closure may return null – it filters nulls automatically.
  • DO use sort(false) or toSorted() when you need to preserve the original list.
  • DO use withDefault() on maps to avoid null checks and if (!map[key]) patterns.
  • DO check isNumber() / isInteger() before calling toInteger() / toDouble().
  • DO use the List form of execute() for system commands with spaces in arguments.
  • DON’T confuse collect() (transforms and returns new list) with each() (side effects, returns original).
  • DON’T assume sort() is non-mutating – without false parameter, it modifies the original list.
  • DON’T use GDK’s Date methods in new code when java.time is available – the GDK Date extensions exist for legacy compatibility.
  • DON’T forget that GDK methods are defined in DefaultGroovyMethods, StringGroovyMethods, and ResourceGroovyMethods – check those classes in the Groovy API docs when you need the full signature.

Conclusion

The GDK is what makes Groovy feel like a better Java. It takes the JDK classes you already know – String, List, Map, File, Number, Object – and adds hundreds of methods that eliminate boilerplate and make common tasks one-liners. collect() replaces streams for mapping, findAll() for filtering, groupBy() for categorization, .text for file I/O, and times() for simple loops. These are not Groovy-specific APIs you have to learn from scratch – they are natural extensions of classes you already use.

Understanding how the GDK works – static methods in helper classes like DefaultGroovyMethods dispatched as instance methods – demystifies Groovy’s “magic” and helps you debug when things don’t behave as expected. And knowing the full toolkit means you write less code, with fewer bugs, in less time.

For more on specific GDK areas, see our detailed guides: Groovy Strings for the full string toolkit, Groovy Collections for list and set mastery, Groovy Maps for map operations, and Groovy File I/O for file handling. To understand how Groovy scripts use the GDK, see our previous post: Scripting vs Class-Based Execution.

Up next: Groovy Design Patterns

Frequently Asked Questions

What is the GDK in Groovy?

The GDK (Groovy Development Kit) is Groovy’s extension layer on top of the Java JDK. It adds hundreds of convenience methods to standard Java classes like String, List, Map, File, Object, and Number. These methods are defined as static methods in helper classes like DefaultGroovyMethods and StringGroovyMethods, but Groovy’s runtime dispatches them as if they were instance methods on the target class.

How does the GDK add methods to Java classes without modifying them?

The GDK uses extension methods. When you call list.collect { ... }, Groovy translates it to DefaultGroovyMethods.collect(list, closure) – a static method where the first parameter is the target object. This dispatching happens at the Groovy runtime level, so the original Java classes are never modified. The extension classes (DefaultGroovyMethods, StringGroovyMethods, etc.) are part of the Groovy runtime library.

What is the difference between collect() and each() in Groovy?

collect() transforms each element and returns a new list of results – it is equivalent to Java’s stream().map(). each() iterates over elements for side effects (like printing) and returns the original collection unchanged. Use collect() when you need transformed data, and each() when you just need to do something with each element.

Does Groovy’s sort() method modify the original list?

Yes, by default sort() mutates the original list in place. To get a new sorted list without modifying the original, use sort(false) or toSorted(). This is a common gotcha for developers coming from Java Streams where sorted() always returns a new stream.

What are the main GDK helper classes?

The main GDK classes are: DefaultGroovyMethods (adds methods to Object, Collection, Map, Number, and more – over 500 methods), StringGroovyMethods (adds methods to String and CharSequence – about 180 methods), ResourceGroovyMethods (adds methods to File and URL), and IOGroovyMethods (adds methods to streams and readers). All are in the org.codehaus.groovy.runtime package.

How do I read a file in one line using Groovy’s GDK?

Use new File('path.txt').text to read the entire file as a string, or new File('path.txt').readLines() to get a List<String> of lines. For line-by-line processing, use new File('path.txt').eachLine { line -> ... }. These methods are added by the GDK’s ResourceGroovyMethods class and handle encoding and resource cleanup automatically.

Previous in Series: Scripting vs Class-Based Execution in Groovy

Next in Series: Groovy Design Patterns

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 *