-
Enhancement
-
Resolution: Unresolved
-
P4
-
None
-
None
Summary
I hereby propose adding static Writer.of(StringBuilder) factory method, providing a non-synchronized Writer implementation optimized for writing into StringBuilder.
This idea was originally discussed as the broader approach Writer.of(Appendable) on the core-libs-dev mailing list, but then limited to the narrow case of Writer.of(StringBuilder) to reduce compexity and focus on the largest actual real-world benefit (see https://mail.openjdk.org/pipermail/core-libs-dev/2025-March/141272.html).
The following sections summarize and update the last state of that discussion.
Problem
Since Java 1.1 we have the StringWriter class. Since Java 1.5 we have the Appendable interface. StringBuilder, StringBuffer and CharBuffer are first-class implementations of it in the JDK, and there might exist third-party implementations of non-String text sinks. Until today, however, we do not have a Writer for Appendables, but need to go costly detours, like producing copies from StringWriter-provided Strings or going through synchronized StringWriter code even in single-threaded environments.
Text sinks in Java are expected to implement the Writer interface. Libraries and frameworks expect application code to provide Writers to consume text produced by the library or framework, for example. Application code often wants to modify the received text, e. g. embed received SVG text into in a larger HTML text document, or simply forward the text as-is to I/O, so mutable StringBuilder or CharBuffer is what real-world application code actually uses, but not immutable Strings! In such cases, taking the StringWriter.toString() detour might be common but is rather inefficient: It implies duplicating the *complete* text for the sole sake of creating a *temporary but otherwise unwanted and not needed* String, while the subsequent processing will copy the data *anyways* or just uses a small piece of it. This eats up time and memory uselessly, and increases GC pressure. Also, StringWriter is synchronized (not explicitly, but de-facto, as it uses StringBuffer internally), which implies another *needless* slowdown. In many cases, the synchronization has no use at all, as in real-world applications least Writers are actually accessed concurrently. As a result, today the major benefit of StringBuilder over StringBuffer (being non-synchronized) vanishes as soon as a StringWriter is used to provide its content. This means, "stringBuilder.append(stringWriter.toString())" imposes slower performance than *essentially* needed, in two ways: toString(), synchronized.
Solution
In an attempt to improve performance of this rather typical use case, I like to contribute a pull request providing the new public factory method java.io.Writer.of(StringBuilder). While Writer.of(Appendable) would cover more cases *in theory* and would be symmetrical to the solution we implemented inJDK-8341566 for the reversed case, java.io.Reader.of(CharSequence), in reality most code written these days actually does abstain from using any other Appendables besides StringBuilder specifically, while covering those other Appendables would impose a lot of open questions and complexity:
* What about Writer.flush() and Writer.close()?
* What if the Appendable is a Writer itself?
* What if the Appendable is Closable but not Flushable, or Flushable but not Closable, or nothing of these, or all of these?
* What if the Applicaton deliberately separates Appendable, Flushable and Closable into different classes, do we want Write.of(Appendable, Flushable, Closable) then?
* Writer.of(StringBuffer) would be implicitly synchronized in turn, so what would be its benefit?
* Are there Appendables besides StringBuilder, StringBuffer and CharBuffer used widely in reality?
* ...
Due to that, Chen Liang and me came to the conclusion that *for now* it makes sense to limit the scope of this new API to provide Writer.of(StringBuilder) only, ignoring all other kinds of Appendable text sinks for now. Following the Pareto Principle, this allows us to cover 80% of the use cases of Text-Sink-Writers by introducing just 20% of the needed complexity.
Alternatives:
- Applications could use Apache Commons IO's StringBuilderWriter. As it is an open-source third-party dependency, some authors might not be allowed to use it, or may not want to carry this additional burden just for the sake of this single performance improvement. In addition, that library is not actively modernized; its Java baseline still is Java 8. There is no commercial support.
- Applications could write their own Writer implementation. Given the assumption that this is a rather common use case, this imposes unjustified additional work for the authors of thousands of applications. It is hard to justify why there is a StringWriter but not a Writer for StringBuilder.
- Instead of authoring a new Writer factory method, we could slightly modify StringWriter, so it uses StringBuilder (instead of StringBuffer). This (still) results in unnecessary duplication of the full text at toString(), and it will break existing applications due to missing synchronization, and because getBuffer() won't be able to return a StringBuilder anymore. Becoming incompatible is a no-go.
- Instead of authoring a new Writer factory method, we could write a new StringBuilderWriter class (like Apache did). That piles up the amount of public classes, which was the main reason inJDK-8341566 to go with the "Reader.of(CharSequence)" factory method instead of the "CharSequenceReader" class. Also it would be confusing to have Reader.of(...) but not Writer.of(...) in the API.
- We could go with a specific Appendable class other than StringBuilder. This would reduce the number of applicable use cases drastically as most applications use explicitly StringBuilder, without providing any considerable benefit over the current proposal.
Option:
- Once we have the specific Writer.of(StringBuilder) method, we eventually can add the broader Writer.of(Appendable) method at a later time, if we see a need for that.
Specification:
The actual specification of the proposed new method "Writer.of(StringBuilder)" is found in the accompanying Github Pull Request, as it is easier to discuss with the actual code change at hand.
I hereby propose adding static Writer.of(StringBuilder) factory method, providing a non-synchronized Writer implementation optimized for writing into StringBuilder.
This idea was originally discussed as the broader approach Writer.of(Appendable) on the core-libs-dev mailing list, but then limited to the narrow case of Writer.of(StringBuilder) to reduce compexity and focus on the largest actual real-world benefit (see https://mail.openjdk.org/pipermail/core-libs-dev/2025-March/141272.html).
The following sections summarize and update the last state of that discussion.
Problem
Since Java 1.1 we have the StringWriter class. Since Java 1.5 we have the Appendable interface. StringBuilder, StringBuffer and CharBuffer are first-class implementations of it in the JDK, and there might exist third-party implementations of non-String text sinks. Until today, however, we do not have a Writer for Appendables, but need to go costly detours, like producing copies from StringWriter-provided Strings or going through synchronized StringWriter code even in single-threaded environments.
Text sinks in Java are expected to implement the Writer interface. Libraries and frameworks expect application code to provide Writers to consume text produced by the library or framework, for example. Application code often wants to modify the received text, e. g. embed received SVG text into in a larger HTML text document, or simply forward the text as-is to I/O, so mutable StringBuilder or CharBuffer is what real-world application code actually uses, but not immutable Strings! In such cases, taking the StringWriter.toString() detour might be common but is rather inefficient: It implies duplicating the *complete* text for the sole sake of creating a *temporary but otherwise unwanted and not needed* String, while the subsequent processing will copy the data *anyways* or just uses a small piece of it. This eats up time and memory uselessly, and increases GC pressure. Also, StringWriter is synchronized (not explicitly, but de-facto, as it uses StringBuffer internally), which implies another *needless* slowdown. In many cases, the synchronization has no use at all, as in real-world applications least Writers are actually accessed concurrently. As a result, today the major benefit of StringBuilder over StringBuffer (being non-synchronized) vanishes as soon as a StringWriter is used to provide its content. This means, "stringBuilder.append(stringWriter.toString())" imposes slower performance than *essentially* needed, in two ways: toString(), synchronized.
Solution
In an attempt to improve performance of this rather typical use case, I like to contribute a pull request providing the new public factory method java.io.Writer.of(StringBuilder). While Writer.of(Appendable) would cover more cases *in theory* and would be symmetrical to the solution we implemented in
* What about Writer.flush() and Writer.close()?
* What if the Appendable is a Writer itself?
* What if the Appendable is Closable but not Flushable, or Flushable but not Closable, or nothing of these, or all of these?
* What if the Applicaton deliberately separates Appendable, Flushable and Closable into different classes, do we want Write.of(Appendable, Flushable, Closable) then?
* Writer.of(StringBuffer) would be implicitly synchronized in turn, so what would be its benefit?
* Are there Appendables besides StringBuilder, StringBuffer and CharBuffer used widely in reality?
* ...
Due to that, Chen Liang and me came to the conclusion that *for now* it makes sense to limit the scope of this new API to provide Writer.of(StringBuilder) only, ignoring all other kinds of Appendable text sinks for now. Following the Pareto Principle, this allows us to cover 80% of the use cases of Text-Sink-Writers by introducing just 20% of the needed complexity.
Alternatives:
- Applications could use Apache Commons IO's StringBuilderWriter. As it is an open-source third-party dependency, some authors might not be allowed to use it, or may not want to carry this additional burden just for the sake of this single performance improvement. In addition, that library is not actively modernized; its Java baseline still is Java 8. There is no commercial support.
- Applications could write their own Writer implementation. Given the assumption that this is a rather common use case, this imposes unjustified additional work for the authors of thousands of applications. It is hard to justify why there is a StringWriter but not a Writer for StringBuilder.
- Instead of authoring a new Writer factory method, we could slightly modify StringWriter, so it uses StringBuilder (instead of StringBuffer). This (still) results in unnecessary duplication of the full text at toString(), and it will break existing applications due to missing synchronization, and because getBuffer() won't be able to return a StringBuilder anymore. Becoming incompatible is a no-go.
- Instead of authoring a new Writer factory method, we could write a new StringBuilderWriter class (like Apache did). That piles up the amount of public classes, which was the main reason in
- We could go with a specific Appendable class other than StringBuilder. This would reduce the number of applicable use cases drastically as most applications use explicitly StringBuilder, without providing any considerable benefit over the current proposal.
Option:
- Once we have the specific Writer.of(StringBuilder) method, we eventually can add the broader Writer.of(Appendable) method at a later time, if we see a need for that.
Specification:
The actual specification of the proposed new method "Writer.of(StringBuilder)" is found in the accompanying Github Pull Request, as it is easier to discuss with the actual code change at hand.
- links to
-
Review(master) openjdk/jdk/24469