1. Overview
In Java, the StringBuilder is widely used for efficiently constructing and manipulating strings, especially when frequent modifications are required. However, once the content is built, we often face the challenge of processing the text line by line, particularly when newline characters are embedded. StringBuilder doesn’t provide a direct method to iterate through lines, which can make tasks such as extracting individual lines or measuring their lengths less straightforward.
In this tutorial, we’ll explore multiple practical techniques for reading a StringBuilder line by line, highlighting the advantages, drawbacks, and ideal scenarios for each method. Further, we’ll try to determine the most effective approach depending on the requirements.
2. Reading a StringBuilder Using Line Separator
One of the simplest ways to process a StringBuilder line by line is to first convert it into a String and then split it at the newline characters.
Since newline conventions vary across platforms (\n, \r\n, or \r), the \\R regex shorthand conveniently matches any type of line break.
Let’s see an example of this approach:
public class LineSeparatorApproach {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(
          "StringBuilder\nLine Separator Approach\r\nLine by Line Reading\rAnother line"
        );
        // \R matches any line break (\n, \r\n, \r)
        String[] lines = sb.toString().split("\\R");
        for (String line : lines) {
            System.out.println(line);
        }
    }
}
This method is simple and readable.
However, it requires loading the entire content of the StringBuilder into memory as a single String before performing the split operation. This means the technique can lead to inefficiencies and increased memory usage when dealing with large volumes of text.
3. Reading a StringBuilder Using Scanner
Another practical way to handle a StringBuilder per line is to wrap its content in a StringReader and use a Scanner to read it line by line.
The Scanner class provides built-in methods like hasNextLine() and nextLine(), which make line-based iteration straightforward and intuitive.
Let’s explore this method with an example:
public class ScannerApproach {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(
            "StringBuilder\nScanner Approach\r\nLine by Line Reading\rAnother line"
        );
        Scanner scanner = new Scanner(new StringReader(sb.toString()));
        while (scanner.hasNextLine()) {
            System.out.println(scanner.nextLine());
        }
        scanner.close();
    }
}
Scanner with StringReader handles smaller text smoothly.
Yet, as the size of the content increases, this method becomes less efficient due to the additional overhead introduced by the Scanner when parsing and managing input. For such cases, the BufferedReader approach usually provides better performance.
4. Reading a StringBuilder Using BufferedReader
To efficiently handle larger StringBuilder content, a better alternative to the Scanner approach is to use a BufferedReader.
By wrapping the StringBuilder content inside a StringReader and then passing it to a BufferedReader, we can iterate through the text line by line with lower overhead and improved performance.
So, let’s see a practical demonstration of the BufferedReader method:
public class BufferedReaderApproach {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(
            "StringBuilder\nBufferedReader Approach\r\nLine by Line Reading\rAnother line"
        );
        try (BufferedReader reader = new BufferedReader(new StringReader(sb.toString()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
This approach takes advantage of BufferedReader’s built-in readLine() method, making the process both faster and more convenient.
Still, there’s a more modern way to handle the situation.
5. Reading a StringBuilder Using Stream API
A recommended concise way to read a StringBuilder line by line is to use the Java 8 Stream API.
Java 8 provides the lines() method to convert a string into a sequential stream, identifying line breaks (\n, \r\n, \r), and generating each line as it’s processed.
Let’s see a demonstration of this approach:
public class StreamLinesApproach {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(
          "StringBuilder\nStream Approach\r\nLine by Line Reading\rAnother line"
        );
        sb.toString()
          .lines()
          .forEach(System.out::println);
    }
}
The Stream API’s lines() method is concise and handles all line separators.
Despite that, it uses more memory for very large content and offers less control than Scanner or BufferedReader.
6. Reading a StringBuilder Using Manual Iteration
Manual iteration reads a StringBuilder using charAt() and substring(), enabling precise handling of line breaks and line-by-line processing, making it ideal for large or specially formatted text.
Let’s see an example implementation of the manual iteration approach:
public class ManualIterationApproach {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(
          "StringBuilder\nManual Iteration Approach\r\nLine by Line Reading\rAnother line"
        );
        int start = 0;
        for (int i = 0; i < sb.length(); i++) {
            char c = sb.charAt(i);
            if (c == '\n' || c == '\r') {
                System.out.println(sb.substring(start, i));
                if (c == '\r' && i + 1 < sb.length() && sb.charAt(i + 1) == '\n') {
                    i++; 
                }
                start = i + 1;
            }
        }
        if (start < sb.length()) {
            System.out.println(sb.substring(start));
        }
    }
}
This method offers a high degree of flexibility.
However, it requires careful handling of different newline variations, which makes it more prone to errors compared to other approaches.
7. Conclusion
In this article, we explored five effective approaches to reading a StringBuilder line by line in Java, each offering its own strengths depending on the use case.
Let’s recap. Splitting by line separators is simple and readable, ideal for small to moderate text. Scanner offers convenient line-by-line reading, though it may be slightly slower for larger content due to parsing overhead. BufferedReader improves performance for large text by reading lines efficiently. The Stream API provides a concise, modern, functional-style approach that handles all line separators but offers less low-level control. Manual iteration gives full control over line detection and memory usage, making it suitable for very large or custom-formatted text, though it requires careful handling of line breaks.
The most suitable approach to use ultimately depends on a combination of key factors, including the overall size of the text being processed, the specific performance requirements of the application, and the extent of control required when handling each line.
The code backing this article is available on GitHub. Once you're 
logged in as a Baeldung Pro Member, start learning and coding on the project.