JDK-8325730 : StringBuilder.toString allocation for the empty String
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 21,22,23
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2024-02-12
  • Updated: 2025-03-13
  • Resolved: 2024-02-20
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 21 JDK 22 JDK 23
21.0.7-oracleFixed 22.0.2Fixed 23 b11Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
A DESCRIPTION OF THE PROBLEM :
In Java 17 StringBuilder.toString was calling StringLatin1.newString that returned a literal "" for len == 0. After https://bugs.openjdk.org/browse/JDK-8282429 https://github.com/openjdk/jdk/commit/bab431cc120fe09be371dadef0c1caf79ec9eef4 Java 21 returns a new String object each time.  While it is minor, it still looks like a regression that effects such code paths as deserialisation.

REGRESSION : Last worked in version 17.0.10

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
new StringBuilder().toString() == ""

ACTUAL -
false

---------- BEGIN SOURCE ----------
public class Test {
    public static void main(String[] args) {
        System.out.println(new StringBuilder().toString() == "");
    }
}
---------- END SOURCE ----------

FREQUENCY : always



Comments
JDK-8138614 has been fixed, so the regression fix is spec compliant as of JDK 25. If the spec change JDK-8138614 is ever backported to JDK 21 in a Maintenance Release, then this regression fix JDK-8325730 can also be backported to JDK 21.
13-03-2025

[21u-na] As Oracle backs this out from 21, we will not backport this either.
15-02-2025

As changes in JDK-8325730 is non compliant with the specification in AbstractStringBuilder.toString , will be doing backout of JDK-8325730 from 21.0.7-oracle. https://bugs.openjdk.org/browse/JDK-8349913 will be used to backout the changes
13-02-2025

Started work on JDK-8138614.
12-02-2025

> I can work on JDK-8138614 if people think it's useful to do so. And JDK-8332282 seems like a duplicate of that, unless [~shade] has other ideas. Please :) It feels fairly weird to backport things that are not adherent to the letter of the spec. I closed JDK-8332282 as duplicate of JDK-8138614.
10-02-2025

[~goetz] Well there's not much "status" to JDK-8138614 at least. Way back in JDK 8 I fixed JDK-7174936 and removed requirements in several places where the spec seemed to require creation of a "new" String. In fact the implementation wasn't returning a new String and so was technically in violation. Nobody thought it made sense to "fix" the implementation to conform to the spec, so we relaxed the spec so that it didn't require a new String every time. Of course it's possible to write a program that uses == on String to discern the exact behavior, but nobody ever complained about the behavior. They did complain about the spec's overly restrictive requirements, and so we changed them. Some time later I observed similar spec wording in StringBuilder/Buffer and I thought that similar reasoning applied, so I filed JDK-8138614. However, I got busy with other stuff and never worked on it. Given that strictly speaking the implementation doesn't conform to the spec, we're in the same position as we were with String before, and we can either change the spec or the implementation. I don't think there's a reason against changing the spec, and it doesn't make sense to me to slow down the implementation to match the spec, so I'm in favor of changing the spec. I can work on JDK-8138614 if people think it's useful to do so. And JDK-8332282 seems like a duplicate of that, unless [~shade] has other ideas.
06-02-2025

Hi [~shade], [~jjose], [~redestad], [~smarks], what is the status of JDK-8138614 and JDK-8332282? Did you consider finalizing this when backporting JDK-8325730 to 21? Will the spec actually be relaxed? Will this be backported to 21? Thanks, Goetz
06-02-2025

Fix request [21u] I backport this for parity with 21.0.7-oracle. Medium risk, as it slightly changes behaviour. See also discussion. But we should behave the same as OracleJDK. Clean backport. SAP nightly testing passed.
06-02-2025

This is the way I see the current state: JDK 8, 11: returns new String (compliant, inefficient) JDK 17: returns constant "" (non-compliant, efficient) [done by accident with JDK-8240094 in JDK 15] JDK 21: returns new String (compliant, inefficient) [done by accident with JDK-8282429 in JDK 19] JDK 25: returns constant "" (non-compliant, efficient) [done by JDK-8325730 in JDK 23] It looks that we lean to resolve the non-compliance by amending the spec (JDK-8332282), and allow returning constant "". Backporting to JDK 21 would bring JDK 21 behavior in line with all JDKs later than 17, which might be a good argument in favor of backport in itself. But I would have expected us to resolve JDK-8332282, so that we have an iron-clad argument this is behavior going forward, before backporting to JDK 21, though. I am a bit surprised to see 21.0.7-oracle backport...
05-02-2025

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk21u-dev/pull/1387 Date: 2025-02-05 13:43:01 +0000
05-02-2025

Hi [~shade], the first (oldest) change you mention has a CSR. This change adheres to the description in that CSR. The change after that has no CSR. Also, OracleJDK 21 has this change now. So I think we should backport.
05-02-2025

I filed JDK-8332282 to address the disconnect with specification. I am still leaning to backport this fix to jdk21u to avoid the behavior flapping between releases. [~sgehwolf] [~goetz], do you have opinions about this?
15-05-2024

That spec should probably be rewritten to allow for a pre-allocated `String` to be returned to better reflect long standing behavior. E.g. `StringBuffer` will (somewhat awkwardly) return a cached string on repeat `toString()` calls since https://bugs.openjdk.org/browse/JDK-8013395 (8). Edit: scratch that, except for "" StringBuffer::toString always returns a new String.
15-03-2024

The backport to jdk21u prompted me to look at `AbstractStringBuilder.toString` spec, which says: ``` /** * Returns a string representing the data in this sequence. * A new {@code String} object is allocated and initialized to * contain the character sequence currently represented by this * object. This {@code String} is then returned. Subsequent * changes to this sequence do not affect the contents of the * {@code String}. * * @return a string representation of this sequence of characters. */ @Override public abstract String toString(); ``` I read it as: a new string is _always_ allocated with `toString()`, which means `return ""` is against the spec?
14-03-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk21u-dev/pull/364 Date: 2024-03-13 19:21:42 +0000
14-03-2024

[jdk22u-fix-request] Approval Request from Aleksey Shipilëv Clean backport to fix the regression introduced in JDK 19. Applies cleanly. Tests pass. The benchmark improvements are the same as in mainline.
05-03-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk22u/pull/70 Date: 2024-02-27 15:17:21 +0000
27-02-2024

Changeset: d2590c69 Author: Claes Redestad <redestad@openjdk.org> Date: 2024-02-20 20:28:55 +0000 URL: https://git.openjdk.org/jdk/commit/d2590c69b4efe5aa2b48b08070e0dbafb04ef202
20-02-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/17931 Date: 2024-02-20 16:32:54 +0000
20-02-2024

Yes, this is a regression. The microbenchmark that was added when this optimization was put in place (https://bugs.openjdk.org/browse/JDK-8240094) only covered String API, not StringBuilder.toString. I'll amend that.
20-02-2024

Will fix to eliminate additional string pressure but the user should not rely on equality. Bad practice. Recommend new StringBuilder().toString().isEmpty().
20-02-2024

The observations onmacOS: JDK 21 output : false JDK 11.0.20 output : false JDK 17.0.10 output : true >> Failed
13-02-2024