Skip to content
AgileLogics
6 min read

Files and I/O

Prerequisites: Exceptions and error handling

The GDK adds convenient methods to java.io.File and java.nio.file.Path, so most file tasks take one line.

def file = new File('notes.txt')

file.text = 'first line\n'           // write (replaces the file)
file << 'second line\n'               // append
file.append('third line\n')

println file.text
println file.readLines().size()
println file.exists()
println file.length()
first line
second line
third line

3
true
34

file.text reads the whole file as a string; assigning to it writes the file. Both use UTF-8 by default on current JDKs — pass a charset explicitly (file.getText('UTF-8')) when a file might come from elsewhere.

Reading line by line

def csv = new File('scores.csv')
csv.text = '''name,score
ada,91
linus,78
grace,88
'''

csv.eachLine { line, number ->
    if (number == 1) return          // skip the header
    def (name, score) = line.split(',')
    println "${name.padRight(6)} ${score.toInteger() >= 80 ? 'pass+' : 'pass'}"
}

def best = csv.readLines().drop(1)
    .collect { it.split(',') }
    .max { it[1].toInteger() }
println "best: ${best[0]}"
ada    pass+
linus  pass
grace  pass+
best: ada

eachLine streams the file one line at a time — use it (or withReader) for large files instead of reading everything with text.

Writing with a writer

def report = new File('report.txt')
report.withWriter('UTF-8') { w ->
    w.writeLine 'Report'
    w.writeLine '======'
    (1..3).each { w.writeLine "item $it" }
}
println report.text

report.withPrintWriter { pw -> pw.printf('%s has %d chars%n', 'Groovy', 6) }
println report.text
Report
======
item 1
item 2
item 3

Groovy has 6 chars

withWriter opens the file, passes a writer to your closure, and closes it afterwards — even if the closure throws.

Directories

def dir = new File('project')
new File(dir, 'src').mkdirs()
new File(dir, 'src/App.groovy').text = 'println "app"'
new File(dir, 'README.md').text = '# Project'
new File(dir, 'build.gradle').text = "plugins { id 'groovy' }"

def files = []
dir.eachFileRecurse { f ->
    if (f.isFile()) files << dir.toPath().relativize(f.toPath()).toString().replace('\\', '/')
}
println files.sort()
println dir.listFiles().findAll { it.name.endsWith('.md') }*.name
println dir.deleteDir()
println dir.exists()
[README.md, build.gradle, src/App.groovy]
[README.md]
true
false

eachFileRecurse walks a directory tree; deleteDir removes a directory and everything inside it. The iteration order of files depends on the file system, so sort results when order matters.

Paths (java.nio)

import java.nio.file.*

def path = Path.of('data.txt')
path.text = 'alpha\nbeta\n'
println path.readLines()
println Files.size(path)
println path.fileName
Files.delete(path)
println Files.exists(path)
[alpha, beta]
11
data.txt
false

Modern Java code prefers Path to File; Groovy gives both the same conveniences.