Writing files in Apache Groovy is surprisingly simple. This guide explores Groovy’s built-in methods that make file handling concise and efficient. (If you haven’t installed Groovy yet, please read the intro to this series.)Â
A couple of years ago, I wrote an article about reading and writing files with Groovy. It’s a very simple demonstration of what Groovy can do differently — and arguably, better — than Java in the context of dealing with text files. In my previous tutorial in the series, I revisited the topic of reading files.
This time, I’m taking a deeper dive into the topic of writing files in Groovy. Quick reminder: Computer files can be broken down into two main categories, text files and binary files. Computer files come in two flavors: text files, like digital documents you can read with a simple editor, and binary files, which store information computers understand but appear jumbled to us (like images or videos).
Java (and Groovy) handle files differently based on type: text (readable) or binary (images, programs). This affects how you process the file’s content.
Just remember, in Java (and Groovy) text files store data as human-readable characters.
In the Java SE API documentation, Unicode code point is used for character values in the range between U+0000 and U+10FFFF, and Unicode code unit is used for 16-bit
char
values that are code units of the UTF-16 encoding (see the official definition of the Character class in the Java language documentation).
Java (and Groovy) defines a hierarchy of classes starting with java.io.Writer
(see this documentation) that are used to write streams of characters — that is, text. Writer
is an abstract class that is inherited, and further developed by:
BufferedWriter
CharArrayWriter
FilterWriter
OutputStreamWriter
PipedWriter
StringWriter
Depending on where you want to send streams of characters, you can obtain specialized versions of these various writers. In this case, wanting to write to a text file, I’m most interested in the java.io.File``Writer
class (see this documentation), which is a subclass of OutputStreamWriter
.
FileWriter
defines a convenient constructor, FileWriter(String fileName)
, that lets you go directly from the name of a file to a writer ready to give you access to that stream of characters.
The problem with FileWriter
is it doesn’t define a handy writeLine()
method. However, unlike the approach with readers, nor does Java’s BufferedWriter
! Instead, Java’s BufferedWriter
separates writing text with write()
from writing line endings with newLine()
.
While this difference isn’t the end of the world, it does add some unnecessary bulk to code. Groovy’s BufferedWriter
does provide a writeLine()
method. Therefore, you can wrap the FileWriter
in a Buffered``Writer
.
Sticking to a mostly Java-esque approach:
1 if (args.length != 1) {
2 System.err.println "Usage: groovy Groovy20a.groovy output-file"
3 System.exit(0)
4}
5 def writer = new BufferedWriter(new FileWriter(args[0]))
6 writer.writeLine("Hello world")
7 writer.writeLine("how's the weather?")
8 writer.close()
Lines one-four deal with usage.
Line five defines the reader you need as a BufferedWriter
instance wrapping a FileWriter
instance that is attached to the filename provided as the first argument on the command line.
Line six to seven write a couple of lines to the file.
Line nine closes the writer.
Let’s run this:
$ groovy Groovy20a.groovy /tmp/junk
$ cat /tmp/junk
Hello world
how's the weather?
$
This was the approach I used to take when writing data to files back in my early Java days. When Java 1.7 came along, it brought with it the java.nio.file.Files
class which removed the need to wrap FileWriter
with BufferedWriter
, by providing a newBufferedWriter()
factory method. I can’t say that I jumped to this immediately since it was simplifying on one hand by adding the complexity of a whole new class on the other. But as I became more familiar with the Files
class, I could see that it consolidated a whole bunch of related utilities into one place, which made it worth learning. For instance, Files
provides the write()
method which takes the java.nio.file.Path
of the file as an argument, which significantly streamlines Java code, by getting rid of the writer
variable, BufferedWriter()
and File``Writer``()
, by accepting an iterator and finally by doing its own close()
. Let’s have a look:
1 import java.nio.file.Files
2 import java.nio.file.Path
3 if (args.length != 1) {
4 System.err.println "Usage: groovy Groovy20b.groovy input-file"
5 System.exit(0)
6 }
7 def outLines = ["foo", "bar"]
8 Files.write(Path.of(args[0]), outLines)
Turns out it streamlines the Groovy code as well.
Running it:
$ groovy Groovy20b.groovy /tmp/junk
$ cat /tmp/junk
foo
bar
$
Examining line eight above, you can see that the write()
method is particularly applicable to the case where you build a list of data to be written in the application and then write it once completed. This could occur, for example, when you want to read a file containing some kind of detail and accumulate, then write out a summary of that sort of detail. Or, of course, just for writing out some package of information that your program has somehow accumulated, perhaps through a user interface.
In cases where you’re likely to be reading and writing at the same time, the Groovy File
class provides a withWriter()
method that calls a closure, passing it a BufferedWriter
instance. You can use this capability to manage a file writer around a loop that reads data from a file:
1 if (args.length != 2) {
2 System.err.println "Usage: groovy Groovy20c.groovy input-file output-file"
3 System.exit(0)
4 }
5 new File(args[1]).withWriter { writer ->
6 new File(args[0]).eachLine { line -> writer.writeLine(line) }
7}
Once again, lines one to four check the usage.
Line five assumes that argument 1 specifies the name of the output file. It uses the withWriter()
method of File
to open this file and attaches an instance of BufferedWriter
to it, finally passing that writer to the closure defined in lines five to seven.
Line six assumes that argument 0 specifies the name of the input file. It opens this file with a (hidden) instance of a BufferedReader
, uses that reader to loop over the lines of the file, using the eachLine()
method of File
to pass every line read to the closure defined in line 6. The closure calls the writeLine()
method of the writer instance to write out each line that was read by the reader. When all the lines in the input file are read, it is automatically closed.
Line seven 7 ends the writer closure and causes the writer to be closed.
You run this as follows:
$ groovy Groovy20c.groovy /etc/group _etc_group
$ head _etc_group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:syslog
tty:x:5:
disk:x:6:
lp:x:7:
mail:x:8:
news:x:9:
$
I’m not going to cover writing binary files here because you need to know what to do with the binary information to structure and interpret it. And in any case, I generally prefer to work with text files whenever possible. This is because it’s much easier to decouple processing steps and view intermediate results when text files are used to communicate between steps.
Nor am I going to cover writing to other sources, like URLs, though Java, and Groovy, provide very similar capabilities to those described above to write to those destinations.
One thing worth mentioning here is that the java.nio
package defines an interesting high-performance I/O model that is worth exploring in applications where there is a lot of data to be processed, or where non-blocking I/O is required (for instance, working with remote systems). And Groovy is there to help.
Conclusion
While Java’s early take on Writers
seems a bit heavy, with the need to wrap a FileWriter
in a BufferedWriter
, use a loop to iterate over the lines in the file and remember to close the whole thing at the end. Things have improved since then, both in Java and in Groovy. Now writing a file is brief and straightforward, and a read-write loop equally so.
And once again, you see that the Groovy approach is to make the classes you already know — like File
— more useful by adding new behavior to them. This is more concise than the modern Java approach, which is to add new class hierarchies that add new behavior while consolidating old behavior, posing a steeper learning curve.
One response to “Apache Groovy: Write files with ease”
[…] look at what Groovy provides for reading and parsing JSON and XML. If you missed the last tutorial, check it out or take a look at the whole […]