[External] Feedback JDK-8318761 : Potential Issue in JDK 25 MessageFormat: Use of Internal Exception for Control Flow

weigao china.weigao at gmail.com
Fri Dec 12 02:12:21 UTC 2025


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–3.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 <chen.l.liang at oracle.com> 于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 <core-libs-dev-retn at openjdk.org> on behalf of
> weigao <china.weigao at gmail.com>
> *Sent:* Wednesday, December 10, 2025 9:31 PM
> *To:* core-libs-dev at openjdk.org <core-libs-dev at openjdk.org>
> *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 <china.weigao at gmail.com> 于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 at 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20251212/8fee56e7/attachment-0001.htm>


More information about the core-libs-dev mailing list