Hi Vincent,
Thanks for the details. Our engineers have created a patch at 
https://github.com/openjdk/jdk/pull/28806.
We seek to backport this to the 25 updates once this is merged. Ideally this 
would be part of update 25.0.3.
Thank you again for this performance regression report!

-Chen
________________________________
From: weigao <[email protected]>
Sent: Thursday, December 11, 2025 8:12 PM
To: Chen Liang <[email protected]>
Cc: [email protected] <[email protected]>
Subject: Re: [External] Feedback JDK-8318761 : Potential Issue in JDK 25 
MessageFormat: Use of Internal Exception for Control Flow


Hi , Chen Liang
Thanks for the clarification.
I agree this IllegalArgumentException is an internal exception used for control 
flow; it’s not printed externally by default.
However, if it is thrown/caught frequently on a hot path, it can still incur a 
noticeable overhead (e.g., fillInStackTrace).

We were able to notice this because our team monitors common JDK internal 
exceptions in our production workloads to validate whether changes in each 
release match our expectations.
So even though this exception is not normally surfaced, we still observed the 
behavior.

Below I’m adding a more concrete reproduction and benchmark data, for your 
evaluation of whether this is worth addressing/backporting in 25u.

## Reproducible benchmark (JMH) and results (JDK 21 vs JDK 25)
I wrote a minimal JMH benchmark to simulate the cost of this pattern:
```

private static final String CHOICE_PATTERN = "{0,choice,0#|1#{1}|2#{1} ({2})}";

@Param({"0", "1", "2"})
public int choiceValue;

private Object[] args;

@Setup(Level.Iteration)
public void setup() {
    args = new Object[]{choiceValue, "us_en", "UTF-8"};
}

@Benchmark
public void messageFormat_choice_static(Blackhole bh) {
    String result = MessageFormat.format(CHOICE_PATTERN, args);
    bh.consume(result);
}

```

Pattern: {0,choice,0#|1#{1}|2#{1} ({2})}
Method: call MessageFormat.format(pattern, args) (creates a new MessageFormat 
instance and parses the pattern each time) to amplify parsing/branch costs; 1 
thread; warmup 3x10s; measurement 5x10s; fork 1.
Key results (Throughput, ops/ms):

JDK 21.0.5: ~2379 to ~3204 ops/ms (depending on choiceValue = 0/1/2)
JDK 25: ~866 to ~1029 ops/ms

```

# JMH version: 1.37
# VM version: JDK 21.0.5, OpenJDK 64-Bit Server VM, 21.0.5+11-LTS

# Warmup: 3 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time


Benchmark                                       (choiceValue)   Mode  Cnt     
Score     Error   Units
MessageFormatBench.messageFormat_choice_static              0  thrpt    5  
3204.770 ± 343.887  ops/ms
MessageFormatBench.messageFormat_choice_static              1  thrpt    5  
2513.444 ± 380.943  ops/ms
MessageFormatBench.messageFormat_choice_static              2  thrpt    5  
2379.665 ± 152.148  ops/ms

```


```

# JMH version: 1.37
# VM version: JDK 25, OpenJDK 64-Bit Server VM, 25+36-3489


# Warmup: 3 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time

Benchmark                                       (choiceValue)   Mode  Cnt     
Score    Error   Units
MessageFormatBench.messageFormat_choice_static              0  thrpt    5  
1029.018 ± 16.425  ops/ms
MessageFormatBench.messageFormat_choice_static              1  thrpt    5   
932.762 ± 12.648  ops/ms
MessageFormatBench.messageFormat_choice_static              2  thrpt    5   
866.487 ± 49.584  ops/ms

```

This is roughly a 2.5x�C3.1x throughput regression on JDK 25 compared to JDK 21.

Note: the benchmark configuration is consistent between the two runs.

Best regards,
Vincent Gao

Chen Liang <[email protected]<mailto:[email protected]>> 
于2025年12月12日周五 00:52写道:
Hello Vincent Gao,
I saw your Java Bugs submission. Unfortunately your stacktrace caused some 
confusion to our triage because it is an internal exception and that trace is 
never printed anywhere.
Your suggestion is a reasonable enhancement, that we should not use ad-hoc 
exceptions for control flow, given they need to fill stack traces, which is 
extremely costly.
Since this is a cleanup, we might commit this to mainline JDK first. If this 
proves to be a performance bottleneck on 25 updates, we can backport.
Since you are writing here, I assume this has a non-negligible impact on 
performance.
If you can share a flame graph showing the regression from this exception, or a 
benchmark difference between 21 and 25.0.1, this would be extremely helpful.

Regards,
Chen Liang
________________________________
From: core-libs-dev 
<[email protected]<mailto:[email protected]>> on 
behalf of weigao <[email protected]<mailto:[email protected]>>
Sent: Wednesday, December 10, 2025 9:31 PM
To: [email protected]<mailto:[email protected]> 
<[email protected]<mailto:[email protected]>>
Subject: Re: [External] Feedback JDK-8318761 : Potential Issue in JDK 25 
MessageFormat: Use of Internal Exception for Control Flow

hi , because of ``private static FormatStyle fromString(String text)` throw 
exception and the try catch exception just set the deault value 
`FormatStyle.SUBFORMATPATTERN`  , so why just make this function retuen this 
value like ? Is it better , right  ?
```

private static FormatStyle fromString(String text) {
    for (FormatStyle style : values()) {
        // Also check trimmed case-insensitive for historical reasons
        if (style != FormatStyle.SUBFORMATPATTERN &&
                text.trim().compareToIgnoreCase(style.text) == 0) {
            return style;
        }
    }
    return FormatStyle.SUBFORMATPATTERN;
}

```

weigao <[email protected]<mailto:[email protected]>> 于2025年12月11日周四 
11:21写道:

Hello,

I would like to report a potential design issue in the JDK 25 implementation of 
MessageFormat.

While working with java.util.Locale#getDisplayName(), I found that JDK 
internals throw and catch an IllegalArgumentException due to the following 
pattern added in the resource bundles:

  *   sun.util.resources.cldr.LocaleNames

  *   sun.util.resources.LocaleNames

The pattern in question is:

DisplayNamePattern: {0,choice,0#|1#{1}|2#{1} ({2})}


This change originates from the following commit:
adoptium/jdk@00ffc42 ― which adds a pattern for MessageFormat.
However, choice is not a valid type for FormatStyle.

As a result, calling Locale#getDisplayName() triggers the following exception 
inside JDK code:

java.lang.IllegalArgumentException
    at 
java.base/java.text.MessageFormat$FormatStyle.fromString(MessageFormat.java:2013)
    at 
java.base/java.text.MessageFormat.formatFromPattern(MessageFormat.java:1718)
    at 
java.base/java.text.MessageFormat.setFormatFromPattern(MessageFormat.java:1679)
    at 
java.base/java.text.MessageFormat.applyPatternImpl(MessageFormat.java:660)
    at java.base/java.text.MessageFormat.<init>(MessageFormat.java:516)
    at java.base/java.util.Locale.getDisplayName(Locale.java:2309)


The implementation currently relies on exception-based logic:

try {
    fStyle = FormatStyle.fromString(style);
} catch (IllegalArgumentException iae) {
    fStyle = FormatStyle.SUBFORMATPATTERN;
}


I understand that MessageFormat catches this exception and falls back to 
SUBFORMATPATTERN, but using exceptions to control expected logic paths may not 
be ideal―especially since FormatStyle.fromString() is only used by 
MessageFormat.

A potentially cleaner approach could be to have FormatStyle.fromString() return 
FormatStyle.SUBFORMATPATTERN directly when encountering unknown style 
identifiers, instead of throwing an exception.

JDK Version Observed:

OpenJDK Runtime Environment Temurin-25.0.1+8 (build 25.0.1+8-LTS)


Please let me know if this behavior is intentional, or if it should be 
considered for improvement.

Best regards,
vincent gao

Reply via email to