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

  1. Set autoApply=false on all but one of the colliding converters, keeping a single winner per domain type
  2. Use @ConverterRegistration on the package to explicitly register the intended one (and disable/demote the others)
  3. Where both converters are genuinely needed, mark one overrideable=false-avoiding: apply the alternative explicitly per attribute with @Convert instead of auto-apply
  4. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/b4bfe72f54868b70. Report an issue: GitHub.