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

  1. Consolidate to exactly one @ConverterRegistration per domain type — search the whole project for '@ConverterRegistration' and the conflicting domain type
  2. 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
  3. 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

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


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