{"record":{"id":"96e2768262dc755a","repo":"apache/druid","slug":"conflicting-key-s-calculated-via-keymapper-for-o","errorCode":null,"errorMessage":"Conflicting key[%s] calculated via keyMapper for original key[%s]","messagePattern":"Conflicting key\\[(.+?)\\] calculated via keyMapper for original key\\[(.+?)\\]","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/utils/CollectionUtils.java","lineNumber":122,"sourceCode":"    final Map<K, V2> result = Maps.newHashMapWithExpectedSize(map.size());\n    map.forEach((k, v) -> result.put(k, valueMapper.apply(v)));\n    return result;\n  }\n\n  /**\n   * Returns a transformed map from the given input map where the key is modified based on the given keyMapper\n   * function. This method fails if keys collide after applying the  given keyMapper function and\n   * throws a IllegalStateException.\n   *\n   * @throws ISE if key collisions occur while applying specified keyMapper\n   */\n  public static <K, V, K2> Map<K2, V> mapKeys(Map<K, V> map, Function<K, K2> keyMapper)\n  {\n    final Map<K2, V> result = Maps.newHashMapWithExpectedSize(map.size());\n    map.forEach((k, v) -> {\n      final K2 k2 = keyMapper.apply(k);\n      if (result.putIfAbsent(k2, v) != null) {\n        throw new ISE(\"Conflicting key[%s] calculated via keyMapper for original key[%s]\", k2, k);\n      }\n    });\n    return result;\n  }\n\n  /**\n   * Creates an immutable map by mapping each entry in the given collection to\n   * a key and a value.\n   */\n  public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> keyMapper, Function<E, V> valueMapper)\n  {\n    return collection.stream().collect(Collectors.toMap(keyMapper, valueMapper));\n  }\n\n  /**\n   * Returns a LinkedHashMap with an appropriate size based on the callers expectedSize. This methods functionality\n   * mirrors that of com.google.common.collect.Maps#newLinkedHashMapWithExpectedSize in Guava 19+. Thus, this method\n   * can be replaced with Guava's implementation once Druid has upgraded its Guava dependency to a sufficient version.","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/utils/CollectionUtils.java#L104-L140","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nMap<String, V> out = CollectionUtils.mapKeys(map, k -> k.toLowerCase()); // collides on case\n// after\nMap<String, V> out = CollectionUtils.mapKeys(map, k -> k); // keep original keys, or merge explicitly\nMap<String, V> merged = map.entrySet().stream().collect(\n    Collectors.toMap(e -> e.getKey().toLowerCase(), Map.Entry::getValue, (a, b) -> a));","handlingStrategy":"validation","validationCode":"Set<Object> mapped = map.keySet().stream().map(keyMapper).collect(Collectors.toSet());\nif (mapped.size() != map.size()) {\n  throw new IllegalStateException(\"keyMapper is not injective over input keys\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  result = CollectionUtils.mapKeys(map, keyMapper);\n} catch (IllegalStateException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Conflicting key\")) {\n    // rebuild with a merging collector\n  } else throw e;\n}","preventionTips":["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"],"tags":["java","illegal-state","map-transformation","key-collision"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}