hibernate/hibernate-orm · error · HibernateException
Multiple auto-apply converters matched %s [%s.%s] : %s
Error message
Multiple auto-apply converters matched %s [%s.%s] : %s
What it means
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.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java:191
private static ConverterDescriptor<?,?> pickUniqueMatch(
MemberDetails memberDetails,
ConversionSite conversionSite,
List<ConverterDescriptor<?,?>> matches) {
return switch ( matches.size() ) {
case 0 -> null;
case 1 -> matches.get( 0 );
default -> {
final var filtered =
matches.stream()
.filter( match -> !match.overrideable() )
.toList();
if ( filtered.size() == 1 ) {
yield filtered.get( 0 );
}
else {
// otherwise, we had multiple matches
throw new HibernateException(
String.format(
Locale.ROOT,
"Multiple auto-apply converters matched %s [%s.%s] : %s",
conversionSite.getSiteDescriptor(),
memberDetails.getDeclaringType().getName(),
memberDetails.getName(),
matches.stream().map( value -> value.getAttributeConverterClass().getName() )
.collect( Collectors.joining( ", " ) )
)
);
}
}
};
}
private List<ConverterDescriptor<?,?>> getMatches(
MemberDetails memberDetails,
ConversionSite conversionSite,View on GitHub (pinned to fad1729dce)
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
Example fix
// before
@Converter(autoApply = true) public class DurationLongConverter implements AttributeConverter<Duration, Long> {}
@Converter(autoApply = true) public class DurationStringConverter implements AttributeConverter<Duration, String> {}
// after
@Converter(autoApply = true) public class DurationLongConverter implements AttributeConverter<Duration, Long> {}
@Converter public class DurationStringConverter implements AttributeConverter<Duration, String> {} // applied per-field
// where the string form is wanted:
@Convert(converter = DurationStringConverter.class)
private Duration reportingPeriod; Defensive patterns
Strategy: validation
Validate before calling
// at startup, detect two autoApply converters covering the same domain type:
Map<Class<?>, List<Class<?>>> autoApplyByDomain = new HashMap<>();
for (Class<? extends AttributeConverter<?,?>> c : scannedConverters) {
Converter ann = c.getAnnotation(Converter.class);
if (ann != null && ann.autoApply()) {
Class<?> domain = (Class<?>) ((ParameterizedType) c.getGenericInterfaces()[0]).getActualTypeArguments()[0];
autoApplyByDomain.computeIfAbsent(domain, k -> new ArrayList<>()).add(c);
}
}
autoApplyByDomain.forEach((domain, cs) -> {
if (cs.size() > 1) throw new IllegalStateException("Multiple auto-apply converters for " + domain + ": " + cs);
}); Try / catch
catch (HibernateException e) {
if (String.valueOf(e.getMessage()).startsWith("Multiple auto-apply converters matched")) {
throw new IllegalStateException("Set autoApply=false on all but one listed converter or disambiguate with @Convert", e);
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- AttributeConverter class [%s] registered multiple times
- AttributeConverter class [%s] registered multiple times
- Conflicting '@ConverterRegistration' descriptors for attribu
- Attribute was neither a Collection nor a Map : ${erasedType}
- Attribute was not a Map : ${collectionMemberType}
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/b4bfe72f54868b70.
Report an issue: GitHub.