{"record":{"id":"b4bfe72f54868b70","repo":"hibernate/hibernate-orm","slug":"multiple-auto-apply-converters-matched-s-s-s","errorCode":null,"errorMessage":"Multiple auto-apply converters matched %s [%s.%s] : %s","messagePattern":"Multiple auto-apply converters matched (.+?) \\[(.+?)\\.(.+?)\\] : (.+?)","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java","lineNumber":191,"sourceCode":"\n\tprivate static ConverterDescriptor<?,?> pickUniqueMatch(\n\t\t\tMemberDetails memberDetails,\n\t\t\tConversionSite conversionSite,\n\t\t\tList<ConverterDescriptor<?,?>> matches) {\n\t\treturn switch ( matches.size() ) {\n\t\t\tcase 0 -> null;\n\t\t\tcase 1 -> matches.get( 0 );\n\t\t\tdefault -> {\n\t\t\t\tfinal var filtered =\n\t\t\t\t\t\tmatches.stream()\n\t\t\t\t\t\t\t\t.filter( match -> !match.overrideable() )\n\t\t\t\t\t\t\t\t.toList();\n\t\t\t\tif ( filtered.size() == 1 ) {\n\t\t\t\t\tyield filtered.get( 0 );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// otherwise, we had multiple matches\n\t\t\t\t\tthrow new HibernateException(\n\t\t\t\t\t\t\tString.format(\n\t\t\t\t\t\t\t\t\tLocale.ROOT,\n\t\t\t\t\t\t\t\t\t\"Multiple auto-apply converters matched %s [%s.%s] : %s\",\n\t\t\t\t\t\t\t\t\tconversionSite.getSiteDescriptor(),\n\t\t\t\t\t\t\t\t\tmemberDetails.getDeclaringType().getName(),\n\t\t\t\t\t\t\t\t\tmemberDetails.getName(),\n\t\t\t\t\t\t\t\t\tmatches.stream().map( value -> value.getAttributeConverterClass().getName() )\n\t\t\t\t\t\t\t\t\t\t\t.collect( Collectors.joining( \", \" ) )\n\t\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tprivate List<ConverterDescriptor<?,?>> getMatches(\n\t\t\tMemberDetails memberDetails,\n\t\t\tConversionSite conversionSite,","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java#L173-L209","documentation":"During binding, Hibernate collects every auto-apply converter whose domain type matches an attribute. One match wins silently; multiple matches are filtered to non-overrideable ones, and if that still leaves not-exactly-one, HibernateException('Multiple auto-apply converters matched ...') is thrown, listing the site (declaring class.member), the site descriptor, and all matching converter class names.","triggerScenarios":"Two or more @Converter(autoApply=true) converters whose domain types both cover one attribute — e.g. one for Duration and another whose domain type is a supertype or shared interface, or simply two converters written for the same basic type — with neither registration marked overrideable via @ConverterRegistration.","commonSituations":"Adding a project-local converter (e.g. for Duration or LocalDate) when the classpath already has an auto-apply converter for that type; third-party starter jars shipping auto-apply converters that collide with the application's; consolidation of previously separate services into one deployment.","solutions":["Set autoApply=false on all but one of the colliding converters, keeping a single winner per domain type","Use @ConverterRegistration on the package to explicitly register the intended one (and disable/demote the others)","Where both converters are genuinely needed, mark one overrideable=false-avoiding: apply the alternative explicitly per attribute with @Convert instead of auto-apply","Check third-party dependencies for shipped @Converter(autoApply=true) classes if the listed converter names are unfamiliar"],"exampleFix":"// before\n@Converter(autoApply = true) public class DurationLongConverter implements AttributeConverter<Duration, Long> {}\n@Converter(autoApply = true) public class DurationStringConverter implements AttributeConverter<Duration, String> {}\n\n// after\n@Converter(autoApply = true) public class DurationLongConverter implements AttributeConverter<Duration, Long> {}\n@Converter public class DurationStringConverter implements AttributeConverter<Duration, String> {} // applied per-field\n\n// where the string form is wanted:\n@Convert(converter = DurationStringConverter.class)\nprivate Duration reportingPeriod;","handlingStrategy":"validation","validationCode":"// at startup, detect two autoApply converters covering the same domain type:\nMap<Class<?>, List<Class<?>>> autoApplyByDomain = new HashMap<>();\nfor (Class<? extends AttributeConverter<?,?>> c : scannedConverters) {\n    Converter ann = c.getAnnotation(Converter.class);\n    if (ann != null && ann.autoApply()) {\n        Class<?> domain = (Class<?>) ((ParameterizedType) c.getGenericInterfaces()[0]).getActualTypeArguments()[0];\n        autoApplyByDomain.computeIfAbsent(domain, k -> new ArrayList<>()).add(c);\n    }\n}\nautoApplyByDomain.forEach((domain, cs) -> {\n    if (cs.size() > 1) throw new IllegalStateException(\"Multiple auto-apply converters for \" + domain + \": \" + cs);\n});","typeGuard":null,"tryCatchPattern":"catch (HibernateException e) {\n    if (String.valueOf(e.getMessage()).startsWith(\"Multiple auto-apply converters matched\")) {\n        throw new IllegalStateException(\"Set autoApply=false on all but one listed converter or disambiguate with @Convert\", e);\n    }\n    throw e;\n}","preventionTips":["Audit third-party jars for @Converter(autoApply=true) classes before adding them","Keep at most one auto-apply converter per domain type in your own codebase","Prefer explicit @Convert for special cases over adding more auto-apply converters"],"tags":["jpa","attribute-converter","auto-apply","ambiguity"],"backgroundTag":"ambiguous-autoapply-converter","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}