{"record":{"id":"1d6100830abab0e7","repo":"FasterXML/jackson-databind","slug":"multiple-suitable-annotated-creator-factory-method","errorCode":null,"errorMessage":"Multiple suitable annotated Creator factory methods to be used as the Key deserializer for type \"+ClassUtil.nameOf(rawKeyType)","messagePattern":"Multiple suitable annotated Creator factory methods to be used as the Key deserializer for type \"\\+ClassUtil\\.nameOf\\(rawKeyType\\)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/main/java/tools/jackson/databind/deser/jdk/JDKKeyDeserializers.java","lineNumber":201,"sourceCode":"        }\n        return null;\n    }\n\n    private static AnnotatedMethod _findExplicitStringFactoryMethod(DeserializationContext ctxt,\n            List<AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode>> candidates)\n        throws JacksonException\n    {\n        AnnotatedMethod match = null;\n        for (AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode> entry : candidates) {\n            // Note: caller has filtered out invalid candidates; all we need to check are dups\n            if (entry.metadata != null) {\n                if (match == null) {\n                    match = entry.annotated;\n                } else {\n                    // 15-Jun-2021, tatu: Not optimal type or information, but has to do for now\n                    //    since we do not get DeserializationContext\n                    Class<?> rawKeyType = entry.annotated.getDeclaringClass();\n                    throw new IllegalArgumentException(\n\"Multiple suitable annotated Creator factory methods to be used as the Key deserializer for type \"\n                            +ClassUtil.nameOf(rawKeyType));\n                }\n            }\n        }\n        return match;\n    }\n\n    /*\n    /**********************************************************************\n    /* KeyDeserializers implementation\n    /**********************************************************************\n     */\n\n    @Override\n    public KeyDeserializer findKeyDeserializer(JavaType type,\n            DeserializationConfig config, BeanDescription.Supplier beanDescRef)\n    {","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/FasterXML/jackson-databind/blob/87876ca5c0569b4933aec2d30d6225e4b9ba3a43/src/main/java/tools/jackson/databind/deser/jdk/JDKKeyDeserializers.java#L183-L219","documentation":"When a class is used as a Map key, Jackson builds a key deserializer from a delegating Creator (a single-String-arg constructor or static factory method). JDKKeyDeserializers._findExplicitStringFactoryMethod throws IllegalArgumentException if more than one explicit (@JsonCreator-annotated, non-PROPERTIES) factory method taking one String is found, because the choice would be ambiguous. The reported type is the declaring class of the second candidate.","triggerScenarios":"A Map key type declares two or more @JsonCreator static methods that each accept a single String (e.g. valueOf(String) and fromString(String) both annotated); a Map<MyKey, V> where MyKey has multiple annotated factory overloads that collapse to the same single-String signature.","commonSituations":"Adding a fromString factory to a class that already has an annotated valueOf; copying annotations during a refactor; libraries (like records with custom factories) that auto-generate multiple candidate creators; upgrading Jackson so that previously-implicit candidates now become explicit and conflict.","solutions":["Keep exactly one @JsonCreator-annotated single-String factory method on the key class and remove the annotation from the others.","If two factories are genuinely needed, mark the undesired ones with @JsonCreator(mode=Mode.DISABLED) or @JsonIgnore.","For valueOf/fromStyle factories, leave them implicit (no annotation) so Jackson's precedence (valueOf then fromString) applies without ambiguity.","Register a custom KeyDeserializer for the type to bypass factory-based resolution entirely."],"exampleFix":"// before\npublic final class MyKey {\n    @JsonCreator public static MyKey valueOf(String s) { ... }\n    @JsonCreator public static MyKey fromString(String s) { ... }\n}\n\n// after\npublic final class MyKey {\n    @JsonCreator public static MyKey valueOf(String s) { ... }\n    public static MyKey fromString(String s) { ... } // implicit only\n}","handlingStrategy":"validation","validationCode":"// At app startup, introspect key types used as Map keys\nBeanDescription bd = mapper.getSerializationConfig()\n    .introspect(mapper.constructType(MyKey.class));\nlong explicitSingleStringFactories = bd.getFactoryMethodsWithMode().stream()\n    .filter(m -> m.metadata != null\n        && m.annotated.getParameterCount() == 1\n        && m.annotated.getRawParameterType(0) == String.class)\n    .count();\nif (explicitSingleStringFactories > 1) {\n    throw new IllegalStateException(\"MyKey has ambiguous key creators\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep at most one @JsonCreator on single-String factory methods of any class used as a Map key.","Prefer implicit valueOf/fromString (no annotation) so Jackson applies deterministic precedence.","Add a startup test that fails the build if a key type gains a second annotated single-String creator."],"tags":["deserialization","map-key","json-creator","factory-method","illegal-argument"],"backgroundTag":null,"analyzedSha":"87876ca5c0569b4933aec2d30d6225e4b9ba3a43","analyzedAt":"2026-08-11T12:55:24.033Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}