[
https://issues.apache.org/jira/browse/LANG-1795?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Zhongxin Yan updated LANG-1795:
-------------------------------
Description:
EnumUtils.getEnumMap(...) currently collect enum values using
Collectors.toMap(...) without a merge function.Because the default
Collectors.toMap requires the key to be {*}unique{*}, these methods will throw
an IllegalStateException if the provided keyFunction produces duplicate keys.
{code:java}
// code placeholder
Example:
class EnumUtilsDuplicateKeyTest {
enum Color {
RED, ROSE
}
@Test
void testGetEnumMapThrowsOnDuplicateKey() {
assertThrows(IllegalStateException.class, () -> {
EnumUtils.getEnumMap(Color.class, c -> c.name().substring(0, 1));
});
}
} {code}
!image-2025-11-27-23-29-17-928.png|width=722,height=145!
Why this happens
* EnumUtils.getEnumMap(enumClass, keyFunction)constructs a Stream<E> of enum
constants (via stream(enumClass)), and then collect(...)s them into a Map using
Collectors.toMap.
* The call pattern used is the two-argument Collectors.toMap(keyMapper,
valueMapper)(i.e. Collectors.toMap(keyFunction::apply, Function.identity())).
This toMap overload {*}requires keys to be unique{*}. Internally, on
encountering a duplicate key it throws: “java.lang.IllegalStateException:
Duplicate key <theKey>”
* A caller-supplied keyFunction might easily produce duplicate keys — common
examples:
# keyFunction = e -> e.name().toLowerCase() — different-cased enum constants
become identical keys.
# keyFunction = e -> e.name().substring(0, 1) — multiple names starting with
same letter collide.
There are multiple reasonable behaviors when keys collide (keep first, keep
last, throw a clearer exception, or return a multi-valued map). The current
implementation chooses an implicit and undocumented behavior (throw) by
delegating to {{Collectors.toMap}} two-arg overload.
*Proposed fix*
Change the collection step to supply a merge function. A compatible,
conservative default is to *preserve the first-seen value*
was:
EnumUtils.getEnumMap(...) currently collect enum values using
Collectors.toMap(...) without a merge function.Because the default
Collectors.toMap requires the key to be {*}unique{*}, these methods will throw
an IllegalStateException if the provided keyFunction produces duplicate keys.
{code:java}
// code placeholder
Example:
class EnumUtilsDuplicateKeyTest {
enum Color {
RED, ROSE
}
@Test
void testGetEnumMapThrowsOnDuplicateKey() {
assertThrows(IllegalStateException.class, () -> {
EnumUtils.getEnumMap(Color.class, c -> c.name().substring(0, 1));
});
}
} {code}
Why this happens
* EnumUtils.getEnumMap(enumClass, keyFunction)constructs a Stream<E> of enum
constants (via stream(enumClass)), and then collect(...)s them into a Map using
Collectors.toMap.
* The call pattern used is the two-argument Collectors.toMap(keyMapper,
valueMapper)(i.e. Collectors.toMap(keyFunction::apply, Function.identity())).
This toMap overload {*}requires keys to be unique{*}. Internally, on
encountering a duplicate key it throws: “java.lang.IllegalStateException:
Duplicate key <theKey>”
* A caller-supplied keyFunction might easily produce duplicate keys — common
examples:
# keyFunction = e -> e.name().toLowerCase() — different-cased enum constants
become identical keys.
# keyFunction = e -> e.name().substring(0, 1) — multiple names starting with
same letter collide.
There are multiple reasonable behaviors when keys collide (keep first, keep
last, throw a clearer exception, or return a multi-valued map). The current
implementation chooses an implicit and undocumented behavior (throw) by
delegating to {{Collectors.toMap}} two-arg overload.
*Proposed fix*
Change the collection step to supply a merge function. A compatible,
conservative default is to *preserve the first-seen value*
> EnumUtils.getEnumMap throw IllegalStateException when keyFunction produces
> duplicate keys
> -----------------------------------------------------------------------------------------
>
> Key: LANG-1795
> URL: https://issues.apache.org/jira/browse/LANG-1795
> Project: Commons Lang
> Issue Type: Bug
> Components: lang.*
> Affects Versions: 3.20.0
> Reporter: Zhongxin Yan
> Priority: Major
> Attachments: image-2025-11-27-23-29-17-928.png
>
>
> EnumUtils.getEnumMap(...) currently collect enum values using
> Collectors.toMap(...) without a merge function.Because the default
> Collectors.toMap requires the key to be {*}unique{*}, these methods will
> throw an IllegalStateException if the provided keyFunction produces duplicate
> keys.
>
> {code:java}
> // code placeholder
> Example:
> class EnumUtilsDuplicateKeyTest {
> enum Color {
> RED, ROSE
> }
> @Test
> void testGetEnumMapThrowsOnDuplicateKey() {
> assertThrows(IllegalStateException.class, () -> {
> EnumUtils.getEnumMap(Color.class, c -> c.name().substring(0, 1));
> });
> }
> } {code}
> !image-2025-11-27-23-29-17-928.png|width=722,height=145!
> Why this happens
> * EnumUtils.getEnumMap(enumClass, keyFunction)constructs a Stream<E> of enum
> constants (via stream(enumClass)), and then collect(...)s them into a Map
> using Collectors.toMap.
> * The call pattern used is the two-argument Collectors.toMap(keyMapper,
> valueMapper)(i.e. Collectors.toMap(keyFunction::apply, Function.identity())).
> This toMap overload {*}requires keys to be unique{*}. Internally, on
> encountering a duplicate key it throws: “java.lang.IllegalStateException:
> Duplicate key <theKey>”
> * A caller-supplied keyFunction might easily produce duplicate keys — common
> examples:
> # keyFunction = e -> e.name().toLowerCase() — different-cased enum constants
> become identical keys.
> # keyFunction = e -> e.name().substring(0, 1) — multiple names starting with
> same letter collide.
> There are multiple reasonable behaviors when keys collide (keep first, keep
> last, throw a clearer exception, or return a multi-valued map). The current
> implementation chooses an implicit and undocumented behavior (throw) by
> delegating to {{Collectors.toMap}} two-arg overload.
> *Proposed fix*
> Change the collection step to supply a merge function. A compatible,
> conservative default is to *preserve the first-seen value*
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)