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

Chen Liang chen.l.liang at oracle.com
Mon Dec 15 20:23:32 UTC 2025


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 <china.weigao at gmail.com>
Sent: Thursday, December 11, 2025 8:12 PM
To: Chen Liang <chen.l.liang at oracle.com>
Cc: 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 , 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 <chen.l.liang at oracle.com<mailto: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<mailto:core-libs-dev-retn at openjdk.org>> on behalf of weigao <china.weigao at gmail.com<mailto:china.weigao at gmail.com>>
Sent: Wednesday, December 10, 2025 9:31 PM
To: core-libs-dev at openjdk.org<mailto:core-libs-dev at openjdk.org> <core-libs-dev at openjdk.org<mailto: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<mailto: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/20251215/7ae7f5d4/attachment-0001.htm>


More information about the core-libs-dev mailing list