🔥 🔥Practical Open Source is coming 🔥🔥 Propose an article about doing business with Open Source!

Apache Groovy: Write files with ease

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.

Disclaimer: All published articles represent the views of the authors, they don’t represent the official positions of the Open Source Initiative, even if the authors are OSI staff members or board directors.

One response to “Apache Groovy: Write files with ease”

  1. […] 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 […]

Author

Support us

OpenSource.net is supported by the Open Source Initiative, the non-profit organization that defines Open Source.

Trending