Intermediate

I/O Operations in Groovy

Learn console and file I/O in Groovy - formatted output, reading input, writing and reading files, and error handling with Docker-ready examples

Introduction

Input and output (I/O) is how a program talks to the outside world—printing to the terminal, reading what a user types, and persisting data to files. In your Hello World tutorial you saw println, but that only scratches the surface of what Groovy offers.

Groovy inherits the entire java.io and java.nio ecosystem, so anything you can do in Java, you can do in Groovy. The difference is that Groovy layers dozens of convenience methods on top of those classes through its Groovy Development Kit (GDK). A File in Groovy gains handy methods like .text, .eachLine, .withWriter, and the << append operator—turning multi-line Java I/O boilerplate into a single expressive line.

Because Groovy is a multi-paradigm scripting language, its I/O style blends imperative statements with functional touches: you’ll pass closures to methods like eachLine and withWriter, letting the language manage resource cleanup for you. In this tutorial you’ll learn formatted console output, reading from standard input, writing and reading files, and handling I/O errors gracefully.

Formatted Console Output

Beyond println, Groovy gives you print (no trailing newline), C-style printf, sprintf (which returns a string), and rich GString interpolation.

Create a file named formatted_output.groovy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// print does not add a newline; println does
print "No newline here. "
println "But this ends the line."

// String interpolation with GStrings
def name = "Groovy"
def version = 4.0
println "Language: ${name}, Version: ${version}"

// printf for C-style formatted output (%n is a platform newline)
printf("Name: %-10s Score: %5.2f%n", "Ada", 91.5)
printf("Hex: %x, Octal: %o, Char: %c%n", 255, 8, 65)

// sprintf returns a formatted String instead of printing it
def label = sprintf("[%03d]", 7)
println "Padded id: ${label}"

// Triple-quoted strings preserve line breaks; the trailing \ trims the first newline
def report = """\
Report
  Items: ${3}
  Status: OK"""
println report

The %-10s conversion left-justifies the string in a 10-character field, while %5.2f right-justifies a float with two decimal places in a 5-character field. GStrings ("...${expr}...") evaluate any expression inside ${} and are the idiomatic Groovy way to build strings.

Reading Console Input

To read what a user types, wrap standard input in a reader. Groovy adds a newReader() method to System.in that returns a buffered reader ready to use.

Create a file named console_input.groovy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Groovy adds newReader() to any InputStream, returning a BufferedReader
def reader = System.in.newReader()

print "Enter your name: "
def name = reader.readLine()
println "Hello, ${name}!"

print "Enter your age: "
def age = reader.readLine() as Integer   // 'as' coerces the String to an Integer
println "Next year you will be ${age + 1}."

The as Integer coercion converts the text line into a number so arithmetic works. Since Docker containers aren’t interactive by default, you’ll pipe input into the script when running it (shown in the Docker section below).

Writing and Reading Files

This is where Groovy’s convenience methods shine. Setting file.text replaces all content, the << operator appends, and withWriter hands you a writer inside a closure and closes it automatically when the closure finishes.

Create a file named file_io.groovy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// --- Writing ---
def file = new File('greetings.txt')

file.text = "Hello from Groovy!\n"      // replaces the whole file
file << "This line was appended.\n"     // appends to it

// withWriter opens a writer, runs the closure, then closes the stream for you
def languages = ['Groovy', 'Java', 'Kotlin']
new File('languages.txt').withWriter { writer ->
    languages.each { lang ->
        writer.writeLine(lang)
    }
}

// --- Reading ---
// Read an entire file as a single String
println "--- greetings.txt ---"
println file.text

// Read a file line by line; eachLine supplies a 1-based line number
println "--- languages.txt ---"
new File('languages.txt').eachLine { line, number ->
    println "${number}: ${line}"
}

// Read all lines at once into a List
def lines = new File('languages.txt').readLines()
println "Total languages: ${lines.size()}"

The closure passed to withWriter is a functional pattern: Groovy guarantees the underlying stream is flushed and closed even if an error occurs inside the block. The eachLine and readLines methods make processing text files a one-liner compared to manually managing a BufferedReader.

Handling I/O Errors

File operations can fail—a missing file, a permissions issue, a full disk. Groovy uses Java’s exception model, so wrap risky I/O in try/catch. The withReader/withWriter methods still close their streams even when an exception is thrown.

Create a file named io_errors.groovy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def path = 'does_not_exist.txt'

// Reading .text on a missing file throws FileNotFoundException
try {
    def content = new File(path).text
    println content
} catch (FileNotFoundException e) {
    println "Could not read '${path}': file not found"
}

// withReader guarantees the stream is closed even when the read fails
try {
    new File(path).withReader { reader ->
        println reader.readLine()
    }
} catch (FileNotFoundException e) {
    println "withReader failed: ${e.class.simpleName}"
}

println "Program continues after handling the error."

Because the exceptions are caught, the script keeps running and prints the final line. Groovy also supports try-with-resources-style safety through the with* closures, so you rarely need to close streams by hand.

Running with Docker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the official image
docker pull groovy:4.0-jdk17-alpine

# Run the formatted output example
docker run --rm -v $(pwd):/app -w /app groovy:4.0-jdk17-alpine groovy formatted_output.groovy

# Run the file I/O example (writes and reads greetings.txt and languages.txt)
docker run --rm -v $(pwd):/app -w /app groovy:4.0-jdk17-alpine groovy file_io.groovy

# Run the error-handling example
docker run --rm -v $(pwd):/app -w /app groovy:4.0-jdk17-alpine groovy io_errors.groovy

# Run the console input example, piping in the answers (note the -i flag)
printf 'Ada\n36\n' | docker run --rm -i -v $(pwd):/app -w /app groovy:4.0-jdk17-alpine groovy console_input.groovy

The -v $(pwd):/app mount means files written by file_io.groovy appear in your current directory on the host. On Windows PowerShell, use ${PWD} instead of $(pwd). The console input example needs the -i flag so the container keeps stdin open for the piped answers.

Expected Output

Running formatted_output.groovy:

No newline here. But this ends the line.
Language: Groovy, Version: 4.0
Name: Ada        Score: 91.50
Hex: ff, Octal: 10, Char: A
Padded id: [007]
Report
  Items: 3
  Status: OK

Running file_io.groovy:

--- greetings.txt ---
Hello from Groovy!
This line was appended.

--- languages.txt ---
1: Groovy
2: Java
3: Kotlin
Total languages: 3

Running io_errors.groovy:

Could not read 'does_not_exist.txt': file not found
withReader failed: FileNotFoundException
Program continues after handling the error.

Running console_input.groovy with the piped input Ada and 36:

Enter your name: Hello, Ada!
Enter your age: Next year you will be 37.

(The prompts and responses share a line because print does not add a newline.)

Key Concepts

  • println vs print vs printf: println adds a newline, print does not, and printf/sprintf give you C-style format strings with conversions like %s, %d, %5.2f, and %n.
  • GString interpolation: Double-quoted strings evaluate ${expression} inline, making them the idiomatic way to build output in Groovy.
  • GDK convenience methods: Groovy enriches File with .text, <<, .eachLine, .readLines, and .writeLine, collapsing verbose Java I/O into single expressions.
  • Closures manage resources: withWriter and withReader accept a closure and automatically close the underlying stream—even if an exception is thrown inside the block.
  • .text reads/writes whole files: Assigning file.text = "..." replaces content, while reading file.text returns the entire file as a String; use << to append.
  • Type coercion for input: Use as Integer (or .toInteger()) to convert text read from standard input into numbers before doing arithmetic.
  • Java exception model for errors: I/O failures throw checked exceptions like FileNotFoundException; wrap risky operations in try/catch to handle them gracefully and keep the program running.

Running Today

All examples can be run using Docker:

docker pull groovy:4.0-jdk17-alpine
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining