Using Java String lines() to Split Text into Lines
java string lines: Learn how to use Java's String.lines() method to split text into a stream of lines, including handling of line terminators and practical examples.
java string lines requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The String.lines() method, introduced in Java 11, provides a direct way to split a string into a stream of lines. It returns Stream<String> where each element is a line from the original string, with line terminators removed. This method is part of the String class and is particularly useful when you need to process multiline text without manually handling \n, \r\n, or \r separators.
How String.lines() Works
String.lines() uses the line terminator definitions from the Unicode standard. It recognizes \n (LF), \r\n (CRLF), and \r (CR) as line terminators. The method returns a stream that is lazily populated, meaning it does not create an array of all lines upfront. Instead, it splits the string on demand as the stream is consumed.
Here is a minimal example:
String text = "first line\nsecond line\r\nthird line\r"; text.lines().forEach(System.out::println);
Output:
first line
second line
third line
Notice that the trailing \r after "third line" is treated as a terminator, but the stream does not include an empty line after it. If the string ends with a terminator, that final terminator does not produce an extra empty line. An empty string returns an empty stream.
Practical Usage Examples
A common use case is reading a file's content as a single string and then processing each line. For example:
String content = Files.readString(Path.of("data.txt")); long count = content.lines().count();
This counts the lines in the file without manually splitting on \n. Another example is filtering lines that match a pattern:
List<String> errors = content.lines() .filter(line -> line.startsWith("ERROR")) .collect(Collectors.toList());
The stream is sequential by default, but you can call .parallel() if you need parallel processing. However, for typical line processing, sequential is usually sufficient and avoids overhead.
Comparing lines() with split() and BufferedReader
Before Java 11, developers often used String.split("\r?\n|\r") or a BufferedReader to handle lines. Each approach has different characteristics.
| Approach | Returns | Handles Empty Lines | Lazy Evaluation | Best Fit |
|---|---|---|---|---|
String.lines() | Stream<String> | Yes, empty lines are preserved | Yes | In-memory strings, functional processing |
String.split() | String[] | Depends on regex; trailing empty strings removed by default | No (creates full array) | Small strings, when an array is needed |
BufferedReader.readLine() | String (one at a time) | Yes, returns empty string for blank lines | Yes | Reading from files or streams, memory efficiency |
split() uses a regular expression and by default discards trailing empty strings. For example, "a\nb\n".split("\n") returns ["a", "b"], not ["a", "b", ""]. lines() preserves the empty line if it appears in the middle, but does not add one after a trailing terminator.
BufferedReader.readLine() is more efficient when reading from a file because it does not load the entire file into memory. lines() requires the whole string to be present, so it is best suited for strings that are already in memory.
Performance and Memory Considerations
Because lines() is lazy, it avoids allocating an array of all lines at once. The stream internally uses a spliterator that scans the string as elements are requested. This means that for a large string, you can process lines without a large intermediate array. However, the original string itself must remain in memory for the duration of the stream processing.
If you are processing a very large file, reading it entirely into a String with Files.readString() may cause high memory usage. In such cases, a BufferedReader is a better choice because it reads lines incrementally. For moderate-sized strings that are already in memory, lines() is convenient and avoids the overhead of a regex-based split().
Another performance aspect is that lines() does not copy the line content. Each line is a substring of the original string, created via String.substring(), which in Java 7+ copies the character array. So each line is a new String object, but the original string is not copied again.
Handling Edge Cases
Understanding how lines() handles edge cases prevents subtle bugs.
- Empty string:
"".lines().count()returns0. - String with only terminators:
"\n\r\n".lines().count()returns2? Let's verify:"\n\r\n"has two line terminators. The first\nends the first empty line, the second\r\nends the second empty line? Actually, the string is: empty line, then\n, then empty line, then\r\n. So there are two empty lines.lines()returns a stream of two empty strings. Socount()is2. - Trailing terminator:
"a\n".lines().count()returns1(just "a"), not2. The trailing terminator does not create an extra empty line. - Mixed terminators:
"a\rb\nc\r\nd"yields lines "a", "b", "c", "d" because each terminator is recognized. - Null input:
lines()throwsNullPointerExceptionif the string isnull. This is expected since it's an instance method.
Common Mistakes and How to Avoid Them
A frequent mistake is assuming lines() trims whitespace from each line. It does not. If you need to remove leading or trailing spaces, you must call .trim() or strip() on each line:
content.lines().map(String::trim).forEach(...)
Another mistake is using lines() on a string that contains only a single line without any terminator. That works fine; it returns a stream with one element.
Some developers expect lines() to handle \n only, but it also handles \r and \r\n. This is usually an advantage, but if you need to preserve the exact line separator, lines() does not provide it. You would need to use split() with a capturing group or a custom scanner.
Compatibility and Version Requirements
String.lines() was added in Java 11. If you are working with Java 8 or 9, you need an alternative. One common workaround is to use a BufferedReader on a StringReader:
new BufferedReader(new StringReader(text)) .lines() .forEach(System.out::println);
This works in Java 8 because BufferedReader.lines() was added in Java 8. However, it is less direct than String.lines(). If you are on Java 11 or later, prefer the built-in method for clarity and simplicity.
When migrating to Java 11, be aware that lines() is a default method on String and does not require any additional imports. It returns a Stream<String>, so you need to import java.util.stream.Stream if you want to declare the type explicitly.