Reading and writing from files or streams in older versions of Java were error prone and verbose, but slowly, over time, the situation has improved considerably, first with try-with-resources, and then with NIO 2, and with some nice additions over the last year. So here’s a guide on how to read from various sources.
To show you how bad IO used to be in Java 5, check out this example:
try { FileInputStream fin = new FileInputStream("C:\\develop\\file.txt"); int i = 0; try { while ((i = fin.read()) != -1) { System.out.println((char) i); } } catch (IOException e) { throw new UncheckedIOException("Could not load file", e); } finally { if (fin != null) { try { fin.close(); } catch (IOException ignored) { // there really isn't much we can do here beyond logging } } } } catch (FileNotFoundException e) { throw new UncheckedIOException("Could not find file", e); }
You can now replace this with:
try { byte[] bytes = Files.readAllBytes(Path.of("C:\\develop\\file.txt")); } catch (IOException e) { throw new UncheckedIOException("Could not load file", e); }
These two examples aren’t completely identical, because writing the first example to replicate the second requires considerably more lines of clean up code, which almost certainly won’t be correct.
The error handling for all these examples will involve the same exceptions, namely IOException, which as a checked exception, you’ll need to handle. You’ll also get NoSuchFileExpection, which is a subclass of IOException, so you can choose to handle that situation separately, or just handle IOException. In these examples, I’ll show handling the IOException by wrapping the exception in an UncheckedIOException. You should do the appropriate error handling for your situation.
A note on Charsets and encodings
In many of the examples, you will see that I specify StandardCharset.UTF_8 as a parameter.
It is very important that you remember to specify a charset in these situations, because the single parameter constructor uses the system default charset, which may be different from one JVM to another. UTF-8 is a decent default.
The methods in java.nio.files.Files use UTF-8 as the default, rather than the system default encoding, so you do not need to specify the charset here. If you need an alternative charset, there is an overloaded version that takes a Charset object.
Use the constants in StandardCharset when you can, because they are guaranteed to exist on the JVM. If you use the String version, you will have to handle a checked UnsupportedEncodingException, although it is a subclass of IOException.
Quick Links
I have a … | |||||
---|---|---|---|---|---|
Filename | File | Path | InputStream | ||
and I want a … | String | Then do | Then do | Then do | Then do |
byte[] | Then do | Then do | Then do | Then do | |
List<String> | Then do | Then do | Then do | Then do | |
Stream<String> | Then do | Then do | Then do | Then do |
I have a filename …
You’ll need to convert this into a java.nio.files.Path object and then follow the directions using that object.
Path file = Path.of(filename);
This will throw an unchecked InvalidPathException exception if there was a problem converting this filename to a Path object.
I have a File object …
Like with a filename, you’ll want to convert this to a Path object.
Path path = file.toPath()
This also throws the unchecked InvalidPathException if there was a problem.
I have a Path and I want a String
This is fairly easy, as the Files utility class has a method for this:
try { String content = Files.readString(path); } catch (IOException e) { throw new UncheckedIOException("Failed to read string", e); }
I have a Path and I want a byte array
This is also easy:
try { byte[] content = Files.readAllBytes(path); } catch (IOException e) { throw new UncheckedIOException("Failed to read bytes", e); }
I have a Path and I want a List of lines
try { List<String> lines = Files.readAllLines(path); } catch (IOException e) { throw new UncheckedIOException("Failed to read lines", e); }
I have a Path and I don’t want to read all the lines into memory
try (var lines = Files.lines(path) { int sum = lines.mapToInt(Integer::valueOf) .sum() } catch (IOException e) { throw new UncheckedIOException("Failed to read lines", e); }
I have an InputStream and I want a String
For this, we need to read in the bytes and then convert that to a String
try { String content = new String(inputStream.readAllBytes(), StandardCharset.UTF_8); } catch (IOException e) { throw new UncheckedIOException("Failed to read string", e); }
I have an InputStream and I want a byte array
This might be obvious from the previous example:
try { byte[] content = inputStream.readAllBytes(); } catch (IOException e) { throw new UncheckedIOException("Failed to read bytes", e); }
I have an InputStream and I want a List of lines
This is one of the most involved examples, because we need to wrap our InputStream in a InputStreamReader and then a BufferedReader.
try (var isr = new InputStreamReader(inputStream, StandardCharset.UTF_8); var reader = new BufferedReader(isr); var stream = reader.lines()) { List<String> lines = stream.collect(toList()); } catch (IOException e) { throw new UncheckedIOException("Failed to read lines", e); }
I have an InputStream and I don’t want to read the entire contents into memory
try (var isr = new InputStreamReader(inputStream, StandardCharset.UTF_8); var reader = new BufferedReader(isr); var lines = reader.lines()) { int sum = lines.mapToInt(Integer::valueOf) .sum() } catch (IOException e) { throw new UncheckedIOException("Failed to read lines", e); }