What you'll learn

  • Reading and writing whole files with .text
  • Appending with << and writing with withWriter
  • Processing large files with eachLine
  • Turning CSV rows into maps

Groovy adds methods to java.io.File that turn common file tasks into one-liners. Perfect for scripts that prepare test data, process exports or inspect integration files.

Read and write whole files

def csv = new File('orders.csv')
csv.text = 'id,customer,total\nPO-1,Acme,250\n'   // write (replaces the file)
csv << 'PO-2,Globex,90\n'                          // append
println csv.text                                    // read everything
def lines = csv.readLines()                         // a List<String>

Large files: line by line

text and readLines() load the whole file into memory. For big files, stream them:

csv.eachLine { line, number ->
    if (number == 1) return          // skip the header
    def (id, customer, amount) = line.split(',')
    // process one line at a time
}

CSV rows as maps

def header = lines.head().split(',')
def rows = lines.tail().collect { l -> [header, l.split(',')].transpose().collectEntries() }
rows.findAll { it.customer == 'Acme' }*.id

transpose() pairs each header with its value, and collectEntries() turns the pairs into a map — so each row becomes [id:PO-1, customer:Acme, total:250].

Watch out: Splitting on commas breaks on quoted values like "Acme, Inc.". For real-world CSV, use a proper parser (for example Apache Commons CSV, or groovy-csv).

Writers and readers that close themselves

new File('report.txt').withWriter('UTF-8') { w ->
    w.writeLine('Acme: 2 order(s)')
}
new File('orders.csv').withReader('UTF-8') { r -> println r.readLine() }

Like Java’s try-with-resources, withWriter and withReader close the file even if an exception is thrown.

Directories

def dir = File.createTempDir()
dir.eachFile { println it.name }
dir.eachFileRecurse { println it.path }
dir.deleteDir()

The full example

// files.groovy — reading and writing files the Groovy way.
def dir = File.createTempDir()
def csv = new File(dir, 'orders.csv')

// Write: text in one go
csv.text = '''id,customer,total
PO-1,Acme,250
PO-2,Globex,90
PO-3,Acme,1200
'''

// Append
csv << 'PO-4,Initech,40\n'

// Read all lines
def lines = csv.readLines()
println "${lines.size() - 1} data rows"                       // 4 data rows

// Read line by line (memory-friendly for big files), skipping the header
def total = 0
csv.eachLine { line, number ->
    if (number == 1) return                                  // header
    def (id, customer, amount) = line.split(',')
    total += amount.toInteger()
}
println "Total: $total"                                      // 1580

// Parse into maps using the header
def header = lines.head().split(',')
def rows = lines.tail().collect { l -> [header, l.split(',')].transpose().collectEntries() }
println rows.findAll { it.customer == 'Acme' }*.id             // [PO-1, PO-3]

// Write with a writer (closes automatically)
def report = new File(dir, 'report.txt')
report.withWriter('UTF-8') { w ->
    rows.groupBy { it.customer }.each { cust, list -> w.writeLine("$cust: ${list.size()} order(s)") }
}
print report.text

// List and clean up files
dir.eachFile { println "file: ${it.name} (${it.length()} bytes)" }
dir.deleteDir()
println "cleaned up: ${!dir.exists()}"

Try it yourself

Given a folder of .csv files, print each file’s name and its number of data rows (excluding the header), largest first.

Show a solution
def dir = new File('exports')
dir.listFiles({ f -> f.name.endsWith('.csv') } as FileFilter)
   .collect { [name: it.name, rows: it.readLines().size() - 1] }
   .sort { -it.rows }
   .each { println "${it.name}: ${it.rows}" }

Key takeaways

  • .text, << and readLines() cover most small-file needs.
  • Use eachLine for big files.
  • withWriter/withReader close files automatically.
  • Use a real CSV parser for messy data.

Example files