apache/druid · error · IllegalStateException
Conflicting key[ ] calculated via keyMapper for original…
Error message
Conflicting key[%s] calculated via keyMapper for original key[%s]
What it means
CollectionUtils.mapKeys builds a new map by applying keyMapper to each key; since a plain HashMap allows only one entry per resulting key, a mapper that maps two different original keys to the same new key throws this IllegalStateException. It protects callers from silently losing values during key transformation.
Solutions
- Make the keyMapper injective over the input keys — include any distinguishing component that was dropped (case, prefix, suffix)
- Use mapValues or a different collector (e.g. groupingBy with a merge function) if collisions are expected and values should be combined
- Deduplicate the input map's keys first if collisions are known and one value should win
- Pre-check the mapper by collecting mapped keys into a Set and comparing sizes before calling mapKeys
Example fix
// before
Map<String, V> out = CollectionUtils.mapKeys(map, k -> k.toLowerCase()); // collides on case
// after
Map<String, V> out = CollectionUtils.mapKeys(map, k -> k); // keep original keys, or merge explicitly
Map<String, V> merged = map.entrySet().stream().collect(
Collectors.toMap(e -> e.getKey().toLowerCase(), Map.Entry::getValue, (a, b) -> a)); Defensive patterns
Strategy: validation
Validate before calling
Set<Object> mapped = map.keySet().stream().map(keyMapper).collect(Collectors.toSet());
if (mapped.size() != map.size()) {
throw new IllegalStateException("keyMapper is not injective over input keys");
} Try / catch
try {
result = CollectionUtils.mapKeys(map, keyMapper);
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().contains("Conflicting key")) {
// rebuild with a merging collector
} else throw e;
} Prevention
- Verify keyMapper is injective over the input key set
- Prefer Collectors.toMap with a merge function when collisions are possible
- Avoid lossy normalizations (case folding, prefix stripping) in key mappers
When it happens
Trigger: Calling CollectionUtils.mapKeys with a Function whose mapping is not injective over the input map's keys — e.g. mapping keys to lowercase strings, or dropping distinguishing prefixes/suffixes — when the source map contains two keys that collide.
Common situations: Normalizing segment/data-source names (case-folding) before mapping; stripping version or tenant prefixes from keys; mapping enum keys to strings where two enum values share a name.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- A batch appenderator was already created for this peon's…
- A realtime appenderator was already created for this peon's…
- Can't add [ , ] to non-empty…
- Cannot have both awaitChannels and awaitFutures
- Expected single element
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/96e2768262dc755a.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/utils/CollectionUtils.java:122
final Map<K, V2> result = Maps.newHashMapWithExpectedSize(map.size());
map.forEach((k, v) -> result.put(k, valueMapper.apply(v)));
return result;
}
/**
* Returns a transformed map from the given input map where the key is modified based on the given keyMapper
* function. This method fails if keys collide after applying the given keyMapper function and
* throws a IllegalStateException.
*
* @throws ISE if key collisions occur while applying specified keyMapper
*/
public static <K, V, K2> Map<K2, V> mapKeys(Map<K, V> map, Function<K, K2> keyMapper)
{
final Map<K2, V> result = Maps.newHashMapWithExpectedSize(map.size());
map.forEach((k, v) -> {
final K2 k2 = keyMapper.apply(k);
if (result.putIfAbsent(k2, v) != null) {
throw new ISE("Conflicting key[%s] calculated via keyMapper for original key[%s]", k2, k);
}
});
return result;
}
/**
* Creates an immutable map by mapping each entry in the given collection to
* a key and a value.
*/
public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> keyMapper, Function<E, V> valueMapper)
{
return collection.stream().collect(Collectors.toMap(keyMapper, valueMapper));
}
/**
* Returns a LinkedHashMap with an appropriate size based on the callers expectedSize. This methods functionality
* mirrors that of com.google.common.collect.Maps#newLinkedHashMapWithExpectedSize in Guava 19+. Thus, this method
* can be replaced with Guava's implementation once Druid has upgraded its Guava dependency to a sufficient version.View on GitHub (pinned to 9b90983fd2)