DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Optimizing Java Applications for Arm64 in the Cloud
  • Jakarta WebSocket Essentials: A Guide to Full-Duplex Communication in Java
  • Java Is Greener on Arm
  • Mastering Date/Time APIs: Challenges With Java's Calendar and JDK Date/Time APIs

Trending

  • Building a Production-Ready AI Agent in 2026: Beyond the Hello World Demo
  • Designing Effective Meetings in Tech: From Time Wasters to Strategic Tools
  • Your API Authentication Isn’t Broken; It’s Quietly Failing in These 6 Ways
  • Edge Computing in Utility IoT: Two Architecture Patterns That Actually Work
  1. DZone
  2. Coding
  3. Java
  4. 1-Line IO in Java

1-Line IO in Java

Java is often blamed for being overly verbose. While language syntax is important, core libraries mainly dictate the level of verbosity imposed on developers.

By 
Johannes Döbler user avatar
Johannes Döbler
·
Jul. 21, 25 · Analysis
Likes (8)
Comment
Save
Tweet
Share
4.9K Views

Join the DZone community and get the full member experience.

Join For Free

Here is a quick coding challenge for all Java developers: How many lines of code do you need to implement the following tasks, using the JDK and any library of your choice?

(Rules of the game: Lines of code are counted using standard formatting, you need to close any opened resources, the code may throw IOExceptions, except if stated otherwise):

  1. Given a java.io.File, read the content of the file into a byte array.
  2. Given a java.nio.file.Path, read the first n bytes of the path into a byte array.
  3. Given a java.io.InputStream, decode the stream content as UTF-8 and append to a StringBuilder.
  4. Given a java.net.URL, read the content, decode as UTF-8, and return as a list of lines.
  5. Given a java.net.Socket, read the content, decode as ISO-8891-1 and return as a list of trimmed lines.
  6. Given a java.io.File source and java.io.File target, read the source, decode as UTF-8, encode as ISO-8891-1 and write to the target.   
  7. Given a java.io.Reader and a java.io.File, write the Reader content as UTF-8 bytes to the file. 
  8. Given a java.io.Reader and a java.io.File, write the Reader content as UTF-8 bytes to the file, catch and log any exception thrown to a SLF4J logger.
  9. Given a java.io.File, read all bytes and only throw RuntimeExceptions.
  10. Given the name of a classpath resource, read the resource as UTF-8 and return as a String.

Answer

You need 1 line for each task if you use jd.commons, a new commons library introduced in this article.

Java
 
import static java.nio.charset.StandardCharset.*;
import static jd.commons.io.fluent.IO.*; // defines Bytes and Chars
import java.io.*;
import java.nio.file.*;
import org.slf4j.Logger;
import jd.commons.io.Resource;

/*01*/    byte[] result = Bytes.from(file).read().all();
/*02*/    byte[] result = Bytes.from(path).read().first(n);
/*03*/    Bytes.from(in).asUtf8().write().to(sb);
/*04*/    List<String> result = Bytes.from(url).asUtf8().read().lines().toList();
/*05*/    List<String> result = Bytes.from(socket).as(ISO_8859_1).read().lines().trim().toList();
/*06*/    Bytes.from(inFile).asUtf8().write().as(ISO_8859_1).to(outFile);
/*07*/    Chars.from(reader).write().asUtf8().to(file);
/*08*/    byte[] result = Bytes.from(file).read().unchecked().all();
/*09*/    Chars.from(reader).write().asUtf8().silent(e -> log.error("error", e)).to(file);
/*10*/    String result = Resource.of(name).asUtf8().read().all();


The JDK provides all the means to implement the above tasks, but requires various amounts of boilerplate code. Typically, you must create and stack implementations of InputStream, OutputStream, Reader, Writer, especially to do character encoding and decoding, and wrap all in a try-with-resources statement, eventually adding exception management.

To improve the situation jd.commons introduces four new interfaces:

  • jd.commons.io.fluent.ByteSource, can provide a java.io.InputStream
  • jd.commons.io.fluent.ByteTarget, can provide a java.io.OutputStream
  • jd.commons.io.fluent.CharSource, can provide a java.io.Reader
  • jd.commons.io.fluent.CharTarget, can provide a java.io.Writer

It provides factories to produce instances of these interfaces for all kinds of IO-related objects (File, Path, URL, Socket, Blob, Clob, and more):

  • jd.commons.io.fluent.IO.Bytes
  • jd.commons.io.fluent.IO.Chars

Provides default methods and builders on these source and target interfaces, which allow you to 

  • Run read/write operations,  
  • Specify character decoding/encoding,
  • Tweak exception management,

And therefore allowing you to do complex IO operations in a single line, as demonstrated in the coding challenge.

A Bit of Java IO History

Java 1.0 introduced the Reader, Writer, InputStream, OutputStream base classes in package java.io for byte- and character-oriented IO. Subclasses of those provide special functionality and additional APIs. It used java.io.File to represent a file in the local file system, and in order to read from/write to a file, you needed to instantiate a FileInputStream or FileOutputStream and wrap in other streams, readers, or writers to enhance functionality. 

There was no try-with-resources statement, no InputStream/Reader.transferTo. As a consequence, just copying all content from a File/InputStream/Reader to a File/OutputStream/Writer took a dozen lines, and that’s why third-party libraries like Apache commons-io came to the rescue to shorten simple IO operations.

Java 1.7 brought a major improvement on the language level by adding the try-with-resources statement, which allowed us to get rid of those mostly faulty finally blocks. 

In a pattern all too familiar to Java developers, version 1.7 also addressed shortcomings of java.io.File (e.g., limited functionality, poor error handling, performance issues, cross-platform reliability) presented a new solution java.nio.file.Path, just to introduce new problems, in this case, poor usability by expecting developers to call static methods in java.nio.file.Files to access Path features and operations.

Still, there are first attempts to provide 1-line solutions for common IO operations, e.g.

  • Files.readAllBytes(Path)
  • Files.write(Path, byte[], OpenOption...)

Subsequent versions continued to add useful methods to trim down boilerplate code:

Java 9

  • InputStream.readAllBytes(),
  • InputStream.transferTo(OutputStream).

Java 10

  • Reader.transferTo(Writer).

Java 11

  • InputStream.readNBytes(int),
  • Files.readString(Path),
  • Files.writeString(Path, CharSequence, OpenOption…).

Java 25 Discussing

  •  Reader.readAllCharsAsString().

Bringing 1-Line IO to Files

While java.io has slowly improved over the years or even decades, it is nowhere near a 1-line solution for common IO operations.

In the meantime, Kotlin has elegantly overtaken this slow process via extensions used in kotlin-stdlib/kotlin-io, which, for instance, allows you to simply read the byte or char content of a file. 

jd.commons.io.FilePath

In the same spirit jd.commons introduces jd.commons.io.FilePath to represent files in a file system. It combines all features of java.io.File and java.nio.file.Path and adds 1-line IO operations:

Java
 
import java.nio.file.attribute.BasicFileAttributes;
import jd.commons.io.FilePath;

FilePath rootDir = FilePath.ofTempDir();
FilePath subDir = rootDir.resolve("sub").createDirectory();
FilePath file1 = subDir.resolve("test1.txt");
FilePath file2 = subDir.resolve("test2.txt");
      
file1.write().asUtf8().string("hello world");
file2.createLink().to(file1);

BasicFileAttributes attrs = file2.attributes().basic();
System.out.println(file2);
System.out.println("- created at " + attrs.creationTime());
System.out.println("- size " + attrs.size());

System.out.println(subDir);
System.out.println("- child count " + subDir.children().count());
subDir.children().glob("test2.*").delete();
System.out.println("- child count " + subDir.children().count());
subDir.deleteRecursively();


jd.commons.io.FileTree

As shown in the above snippet FilePath.deleteRecursively allows to delete a directory and its content. While this is convenient, operations on a tree of files, i.e., a root directory and all its descendants, are more complex than that: You want to be able to apply different operations on the tree, e.g.:

  • List files
  • Delete files
  • Copy files, etc.

And apply such operations only to a defined subset of the tree, e.g.:

  • Including/excluding the root
  • Including only files matching a filter
  • Include descendants up to a certain depth, etc.

The JDK only provides basic building blocks for file tree operations. First, you can always implement recursion on the tree, by listing the children of a given file:

  • java.io.File.listFiles()
  • java.io.File.listFiles(FileFilter)

Or since 1.7 you can use the walk methods provided by java.nio.file.Files and implement a java.nio.file.FileVisitor:

  • java.nio.file.Files.walkFileTree(Path, Set<FileVisitOptions>, [int], FileVisitor)

Or stream the tree (loosing the capability to skip subtrees):

  •   Stream<Path> java.nio.file.Files.walk(Path). 

A recursive delete based on JDK building blocks can then be archived by:

Java
 
Files.walk(path)
    .sorted(Comparator.reverseOrder())
    .forEach(p -> {
        try {
            Files.delete(p);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    });


This is obviously clumsy. For a full-fledged solution that takes filtering and multiple operations into account, the tree should be represented as its own object:

Java
 
FilePath src = ...
FilePath target = ...
  
// recursively copy all *.txt files to another directory
FileTree.of(src)
    .addFileFilter((file,attr) -> file.getName().endsWith(".txt"))
    .copy().to(target);

// recursively delete the tree except the root directory
FileTree.of(src)
    .setExcludeRoot()
    .delete();


Summary

If you like short and concise code, then please check out https://github.com/jdlib/jd.commons for the goodies presented in this article and other handcrafted utility classes.

Java Development Kit Io (programming language) Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Optimizing Java Applications for Arm64 in the Cloud
  • Jakarta WebSocket Essentials: A Guide to Full-Duplex Communication in Java
  • Java Is Greener on Arm
  • Mastering Date/Time APIs: Challenges With Java's Calendar and JDK Date/Time APIs

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook