Groovy File I/O
last modified October 15, 2026
Groovy extends java.io.File with a large set of convenience
methods, so everyday file handling needs far less ceremony than plain Java.
Reading a file, iterating its lines, writing text, and traversing directories
are all one-liners, and the withReader and withWriter
helpers close streams for you. This tutorial covers reading, writing, copying,
and walking the filesystem with practical examples.
Reading a File Line by Line
The eachLine method processes a file one line at a time, which
keeps memory flat for large files. A closure receives each line as it is read.
def file = new File("data.txt")
file.eachLine { line, number ->
println "$number: $line"
}
The closure receives both the line and its one-based number. Because the stream is opened and closed inside the call, there is no resource cleanup to remember, and only the current line is held in memory.
$ groovy ReadLines.groovy 1: line one 2: line two 3: name,age 4: Alice,30
Reading the Whole File
For small files, text returns the entire content as a single
string, while readLines returns a list of lines.
def file = new File("data.txt")
println file.text
def lines = file.readLines()
println lines.size()
println lines.first()
println lines.last()
The text property yields the full file, and readLines
splits it into a list. These shortcuts are ideal for configuration files and
small fixtures where loading everything at once is acceptable.
$ groovy ReadWholeFile.groovy line one line two name,age Alice,30 4 line one Alice,30
Reading with a Reader and Encoding
When you need a specific character encoding, withReader supplies a
BufferedReader that is closed automatically at the end of the block.
def file = new File("data.txt")
file.withReader("UTF-8") { reader ->
String line
while ((line = reader.readLine()) != null) {
println line
}
}
The explicit UTF-8 encoding avoids the platform default, which may corrupt accented characters. The reader is closed even if an exception is thrown inside the block, guaranteeing no leaked handles.
$ groovy ReadEncoding.groovy line one line two name,age Alice,30
Writing to a File
The simplest way to write text is the write method, but
withWriter is preferred for multiple writes because it manages the
stream for you.
def file = new File("output.txt")
file.write("First line\n")
file.withWriter("UTF-8") { writer ->
writer.writeLine("Second line")
writer.writeLine("Third line")
}
The write call replaces the file content, and the
withWriter block appends its lines to the freshly created file. The
writeLine helper adds the platform line separator automatically.
Appending to a File
To add to an existing file, open it in append mode with
withWriterAppend. The new content is placed after the current end
of file.
def log = new File("app.log")
log.withWriterAppend { writer ->
writer.writeLine("[INFO] application started")
writer.writeLine("[INFO] user logged in")
}
Each run adds two lines to the log, preserving earlier entries. Append mode is the natural fit for logs, audit trails, and other growing records.
Copying Files
The File class offers explicit copy methods as well as the
<< operator, which streams one file into another.
def source = new File("data.txt")
def destination = new File("backup.txt")
source.withInputStream { input ->
destination.withOutputStream { output ->
output << input
}
}
println destination.text
Piping the input stream into the output stream copies the bytes verbatim, including any binary data. Both streams are closed when their blocks exit, so no partial copies are left behind on error.
Listing Directory Contents
The eachFile and listFiles methods enumerate the
entries of a directory. A closure receives each File in turn.
def dir = new File(".")
dir.eachFile { file ->
println "${file.directory ? 'DIR ' : 'FILE'} ${file.name}"
}
Each entry is classified as a directory or file, and its name is printed. The
eachFile variants with a filter or a FileType narrow
the listing without an explicit condition.
Recursive Traversal
To walk an entire tree, use eachFileRecurse. The closure is invoked
for every file and directory at any depth.
def root = new File(".")
def entries = []
root.eachFileRecurse { file ->
entries << file
if (file.name.endsWith(".groovy")) {
println file.path
}
}
println "total entries: ${entries.size()}"
The traversal prints every Groovy source file under the current directory. The
closure also collects each entry into a list, so the total is counted in the same
pass. Change "." to the directory you want to scan. The method itself
returns nothing, so the count has to be collected inside the closure. Recursive traversal is the
basis of source scanners, report generators, and build tooling.
Working with Directories
Creating and removing directories is handled by mkdirs and
deleteDir, which work recursively where appropriate.
def dir = new File("build/tmp")
if (!dir.exists()) {
dir.mkdirs()
}
println dir.directory
dir.deleteDir()
println dir.exists()
The mkdirs call creates missing parents along the path, and
deleteDir removes the directory and everything inside it. These are
convenient primitives for cleaning up build workspaces.
Reading Binary Data
For binary files, bytes returns the raw content as a byte array,
and withInputStream gives streamed access.
def file = new File("image.png")
byte[] data = file.bytes
println "size: ${data.length} bytes"
println data[0..7].collect { String.format("%02X", it) }.join(" ")
The first two bytes of a PNG are the magic number 89 50, which the
hex dump confirms. Reading bytes is the right approach for images, archives, and
other non-text content.
Processing a CSV File
Combining eachLine with split turns a CSV file into
structured records without a dedicated library.
def csv = new File("people.csv")
csv.eachLine { line, number ->
if (number == 1) return
def fields = line.split(",")
println "name=${fields[0].trim()}, age=${fields[1].trim()}"
}
The header row is skipped by checking the line number, and each remaining line is split into fields. For files with quoted commas you would need a real CSV parser, but this covers simple delimited data.
$ groovy CsvFile.groovy name=Alice, age=30 name=Bob, age=25
Best Practices for File I/O
- Use withReader and withWriter: They close streams on both normal and exceptional exit, preventing leaked handles.
- Specify Encoding: Pass UTF-8 explicitly instead of relying on the platform default.
- Stream Large Files: Prefer
eachLineovertextwhen the file may not fit comfortably in memory. - Check Before Deleting: Confirm the path with
existsanddirectorybefore removing anything. - Separate Paths from Data: Build paths with
new File(dir, name)rather than string concatenation.
Source
In this tutorial, we read files line by line and in full, handled encodings,
wrote and appended text, copied files, listed and traversed directories,
created and removed folders, read binary data, and parsed a CSV file. Groovy's
extensions to File make routine file handling compact and safe.
Author
List all Groovy tutorials.