hibernate/hibernate-orm · error · AnnotationException
Conflicting '@ConverterRegistration' descriptors for attribu
Error message
Conflicting '@ConverterRegistration' descriptors for attribute converter '${converterTypeName}' What it means
AttributeConverterManager.checkNotOverriding allows only one RegisteredConversion per domain type: if a registration already exists and the new one is not equal to it, Hibernate throws AnnotationException("Conflicting '@ConverterRegistration' descriptors for attribute converter 'X'"). Equal duplicates are just logged and skipped, so the error specifically means two different registrations compete for the same domain type.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java:106
// See if we have a matching entry in attributeConverterDescriptorsByClass.
// If so, remove it. The conversion being registered will always take precedence.
if ( attributeConverterDescriptorsByClass != null ) {
final var removed = attributeConverterDescriptorsByClass.remove( conversion.getConverterType() );
if ( removed != null && BOOT_LOGGER.isDebugEnabled() ) {
BOOT_LOGGER.removedPotentiallyAutoApplicableConverterDueToRegistration(
removed.getAttributeConverterClass().getName() );
}
}
registeredConversionsByDomainType.put( domainType, conversion );
}
private void checkNotOverriding(RegisteredConversion conversion, Type domainType) {
// make sure we are not overriding a previous conversion registration
final var existingRegistration = registeredConversionsByDomainType.get( domainType );
if ( existingRegistration != null ) {
final String converterTypeName = conversion.getConverterType().getName();
if ( !conversion.equals( existingRegistration ) ) {
throw new AnnotationException( "Conflicting '@ConverterRegistration' descriptors for attribute converter '"
+ converterTypeName + "'" );
}
else {
BOOT_LOGGER.skippingDuplicateConverterRegistration( converterTypeName );
}
}
}
private static Type getDomainType(RegisteredConversion conversion) {
// the registration did not define an explicit domain-type, so inspect the converter
return conversion.getExplicitDomainType().equals( void.class )
? typeArguments( AttributeConverter.class, conversion.getConverterType() )[0]
: conversion.getExplicitDomainType();
}
private Collection<ConverterDescriptor<?,?>> converterDescriptors() {
return attributeConverterDescriptorsByClass == null
? emptyList()View on GitHub (pinned to fad1729dce)
Solutions
- Consolidate to exactly one @ConverterRegistration per domain type — search the whole project for '@ConverterRegistration' and the conflicting domain type
- If different parts of the app need different converters for the same type, disable auto-apply registration for one and apply it explicitly with @Convert on the specific attributes
- Keep registrations centralized in a single package-info (e.g. the root or a converters package) so conflicts are visible in one file
Example fix
// before (two modules, each package-info.java) @ConverterRegistration(domainType = Duration.class, converter = DurationLongConverter.class) @ConverterRegistration(domainType = Duration.class, converter = DurationStringConverter.class) // after: one auto-apply registration; the other applied per-attribute // package-info.java @ConverterRegistration(domainType = Duration.class, converter = DurationLongConverter.class) // entity field needing the other one: @Convert(converter = DurationStringConverter.class) private Duration reportingPeriod;
Defensive patterns
Strategy: validation
Validate before calling
// scan all package-info @ConverterRegistrations and assert one domain type each:
Map<Class<?>, String> byDomain = new HashMap<>();
for (Package p : packages) {
ConverterRegistrations regs = p.getAnnotation(ConverterRegistrations.class);
if (regs == null) continue;
for (ConverterRegistration r : regs.value()) {
String prev = byDomain.put(r.domainType(), r.converter().getName());
if (prev != null && !prev.equals(r.converter().getName())) {
throw new IllegalStateException("Two @ConverterRegistration for " + r.domainType() + ": " + prev + " vs " + r.converter().getName());
}
}
} Try / catch
catch (AnnotationException e) {
if (String.valueOf(e.getMessage()).contains("Conflicting '@ConverterRegistration'")) {
throw new IllegalStateException("Deduplicate @ConverterRegistration for the domain type named in the message", e);
}
throw e;
} Prevention
- Keep all @ConverterRegistration declarations in one package-info for the whole application
- When merging modules, grep for duplicate domainType registrations first
- Use @Convert for per-attribute exceptions instead of a second registration
When it happens
Trigger: Two @ConverterRegistration annotations (typically on package-info.java, possibly across merged modules) whose domainType resolves to the same type but whose converter or settings differ — e.g. domainType=Duration with converter=DurationToStringConverter in one module and DurationLongConverter in another, or the same converter registered twice with different autoApply/override flags.
Common situations: Modular codebases where several modules register converters for shared JDK types (java.time, enums, UUID); merging projects that each carried their own converter registrations; editing a registration while forgetting a copy elsewhere.
Related errors
- Class '<componentClassName>' is an '@Embeddable' type and ma
- Property '<propertyName>' may not be annotated '@BatchSize'
- One to many association '<propertyName>' was annotated '@Col
- Collection '<propertyName>' was annotated '@Collate'
- Root entity '<entityName>' is annotated '@DiscriminatorOptio
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/e66ff65b5d2c7a5e.
Report an issue: GitHub.