On Java 11 or newer, it is one call: String content = Files.readString(Path.of("notes.txt"));. It reads the whole file and decodes it as UTF-8. For an InputStream, use new String(in.readAllBytes(), StandardCharsets.UTF_8) (Java 9+). For a file too big to hold in memory, read it line by line with Files.lines inside a try-with-resources block.
Java has collected a lot of ways to read a file over the years: FileReader, BufferedReader, Scanner, FileInputStream and finally the java.nio.file.Files helpers. The modern answer comes down to two questions. Do you want the file as one String or as lines? And is it small enough to load all at once, or big enough that you should stream it? The other thing to get right is the charset: a file is bytes, and turning bytes into a String always means choosing an encoding. Every snippet below writes its own input file first, because each run starts in an empty folder. Hit Run, then edit the code and run it again.
1Files.readString (Java 11+)Recommended
Files.readString(path) opens the file, reads every byte, decodes them as UTF-8, closes the file and returns a String. There is no stream to close and no loop to write. Path.of (also Java 11) builds the path; a relative path like "notes.txt" is resolved against the working directory. Its mirror image, Files.writeString, creates the input file here.
Output
The file comes back exactly as written: Compile, Run, Share, then 18 chars. That count includes the final newline, because readString keeps line terminators; content.lines() splits on them and gives 3 lines, and strip() trims the trailing one before COMPILE, RUN, SHARE print. To read with a different encoding, pass it as the second argument: Files.readString(path, StandardCharsets.ISO_8859_1). On Java 8, where readString doesn't exist, the equivalent is new String(Files.readAllBytes(Paths.get("notes.txt")), StandardCharsets.UTF_8).
2Files.readAllLines: a List of lines
When you will process the file line by line anyway, Files.readAllLines(path) skips the splitting step and returns a List<String>, one element per line with the terminators removed. It understands \n, \r\n and \r, so a file saved on Windows reads the same as one saved on Linux. Like readString, it loads the whole file into memory and decodes it as UTF-8.
Output
Prints [buy milk, fix bug, , ship release] and 4 lines. The \r\n after the first line was stripped as cleanly as the \n, the blank line survives as an empty string (so line 3 prints nothing after its label), and the last line is read even though the file doesn't end with a newline. String.join turns the list back into one string: buy milk | fix bug | | ship release. For more on joining, see join a list into a string. If you already have the whole file as a String, content.lines().toList() gives the same list.
3Large files: Files.lines and BufferedReader
readString and readAllLines hold the entire file in memory, which is fine for a config file and a problem for a multi-gigabyte log. For those, read one line at a time. Files.lines(path) (Java 8) returns a lazy Stream<String> that reads the file as the stream pulls lines, and Files.newBufferedReader(path) with a readLine() loop is the classic version. Both hold an open file handle, so both belong in a try-with-resources block. This snippet writes a 100,000-line log first.
Output
The stream prints ERROR disk full at line 25000 and ERROR disk full at line 50000, and because limit(2) short-circuits, the stream stops after the second match instead of scanning the remaining 50,000 lines. The loop then counts the whole file: 100000 lines, last: ERROR disk full at line 100000. Only one line is in memory at a time either way. Forgetting the try-with-resources on Files.lines is a real leak: the stream keeps the file open until it is closed, and a terminal operation like forEach does not close it for you. Use the stream when filtering and mapping read naturally, and the readLine() loop when you need to break out, keep state across lines or throw a checked exception from the loop body.
4InputStream to String: readAllBytes and transferTo
Data that doesn't come from a Path arrives as an InputStream: an HTTP response body, a classpath resource, a zip entry, a socket. Since Java 9, in.readAllBytes() reads it to the end, and new String(bytes, StandardCharsets.UTF_8) decodes it. in.transferTo(out) (also Java 9) copies the stream into any OutputStream instead. The snippet uses a ByteArrayInputStream as the stand-in for a real source; the code is the same for any stream.
Output
The first block prints Grüße aus Köln and line two. The transferTo line prints 26 bytes -> 23 chars: ü, ß and ö take two bytes each in UTF-8, which is exactly why the charset argument matters. The last line, Grüße aus Köln / line two, comes from wrapping the stream in an InputStreamReader, the way to go when you want lines rather than one string (and the usual answer on Java 8, where InputStream has no readAllBytes). ByteArrayOutputStream.toString(Charset) needs Java 10. Outside the JDK, Apache Commons IOUtils.toString(in, UTF_8) and Guava's CharStreams do the same job, but on a current JDK you don't need them.
5Which should you use?
| Method | Since | Memory | Best for |
|---|---|---|---|
| Files.readString(path) | Java 11 | Whole file | The default for a text file you want as one String |
| Files.readAllLines(path) | Java 8 | Whole file | Small files you process as a list of lines |
| Files.lines(path) | Java 8 | One line at a time | Large files, filter and map with streams |
| Files.newBufferedReader(path) | Java 8 | One line at a time | Large files with loop logic, early exit or state |
| new String(in.readAllBytes(), UTF_8) | Java 9 | Whole stream | An InputStream: HTTP body, resource, zip entry |
| new Scanner(file).useDelimiter("\\A") | Java 5 | Whole file | Nothing new: an old trick readString replaced |
6Missing files and the wrong charset
Two things go wrong in practice. The file isn't there, and the Files methods throw NoSuchFileException, a subclass of IOException whose message is the path you passed. Or the file isn't UTF-8: a CSV exported from an old Windows program is often Latin-1 or windows-1252, where é is the single byte 0xE9, and that byte on its own is invalid UTF-8.
Output
The missing file prints NoSuchFileException: missing.txt, and the Files.exists check falls back to (defaults). Catch the exception when the file should exist and its absence is an error; check first when a missing file is normal. Then the charset trap: café written as Latin-1 is 4 bytes on disk, and readString refuses to guess, throwing MalformedInputException: Input length = 1. Passing the right charset reads café. The last line shows the quieter alternative: new String(bytes, UTF_8) never throws, it swaps each bad byte for U+FFFD and prints caf� 65533. That is silent data loss, so prefer the exception and fix the charset.
Frequently asked questions
How do I read a file into a String in Java 8?
Files.readString and Path.of arrived in Java 11. On Java 8, use new String(Files.readAllBytes(Paths.get("notes.txt")), StandardCharsets.UTF_8), which produces the same String. The old new Scanner(file).useDelimiter("\\A").next() trick also works (the \A pattern matches only the start of input, so the one token is the whole file), but it is harder to read and there is no reason to use it on Java 11 or newer.
How do I convert an InputStream to a String in Java?
On Java 9 or newer: new String(in.readAllBytes(), StandardCharsets.UTF_8), inside a try-with-resources block that closes the stream. On Java 8, wrap it in a reader and collect the lines: new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n")). Note that this form normalises line endings to \n and drops a trailing newline. Apache Commons IOUtils.toString(in, UTF_8) does the same job if the library is already on your classpath.
What is the difference between Files.readAllLines, Files.lines and BufferedReader?
Files.readAllLines reads the whole file into a List<String> at once, so it is simple but uses memory proportional to the file. Files.lines returns a lazy Stream<String> that reads as you consume it, and it must be closed with try-with-resources. Files.newBufferedReader with a readLine() loop is equally lazy and gives you full control of the loop. For a file of any size, the last two keep only one line in memory.
Why do I get MalformedInputException: Input length = 1?
The file is not valid UTF-8, and Files.readString, readAllLines and newBufferedReader all decode as UTF-8 by default and throw on the first bad byte. (Files.lines throws an UncheckedIOException wrapping the same error.) Pass the charset the file was actually written in, for example Files.readString(path, StandardCharsets.ISO_8859_1) or Charset.forName("windows-1252"). Readers built on InputStreamReader, such as FileReader, don't throw: they replace bad bytes with U+FFFD, which hides the problem.
What charset does FileReader use?
The JVM default charset. Since Java 18 (JEP 400) that is UTF-8 on every platform, and this runtime reports UTF-8 for Charset.defaultCharset(). On Java 17 and older it was the platform encoding, often windows-1252 on Windows, so the same code read files differently per machine. Pass the charset explicitly to be safe on any version: new FileReader(file, StandardCharsets.UTF_8) (Java 11+) or Files.newBufferedReader(path, StandardCharsets.UTF_8).
How do I read a text file from the classpath or resources folder?
Resources inside a JAR are not files, so Files.readString cannot open them. Open them as a stream with Main.class.getResourceAsStream("/data.txt") (the leading slash means the classpath root) and read it with new String(in.readAllBytes(), StandardCharsets.UTF_8). getResourceAsStream returns null rather than throwing when the resource is missing, so check for null (or wrap it in Objects.requireNonNull) before reading.