hibernate/hibernate-orm · error · HibernateException

AttributeConverter class [%s] registered multiple times

Error message

AttributeConverter class [%s] registered multiple times

What it means

AttributeConverterManager keeps one descriptor per converter class in a ConcurrentHashMap; addAttributeConverter uses put() and throws HibernateException('AttributeConverter class [X] registered multiple times') whenever a previous entry existed. Any second registration of the same converter class — regardless of whether the descriptor is identical — is rejected.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/boot/model/convert/internal/AttributeConverterManager.java:71

		if ( registeredConversionsByDomainType != null ) {
			final var domainType = descriptor.getDomainValueResolvedType();
			final var registeredConversion = registeredConversionsByDomainType.get( domainType );
			if ( registeredConversion != null ) {
				// we can skip registering the converter, the RegisteredConversion will always take precedence
				if ( BOOT_LOGGER.isDebugEnabled() ) {
					BOOT_LOGGER.skippingRegistrationAttributeConverterForAutoApply( converterClass.getName() );
				}
				return;
			}
		}

		if ( attributeConverterDescriptorsByClass == null ) {
			attributeConverterDescriptorsByClass = new ConcurrentHashMap<>();
		}

		final Object old = attributeConverterDescriptorsByClass.put( converterClass, descriptor );
		if ( old != null ) {
			throw new HibernateException(
					String.format(
							Locale.ENGLISH,
							"AttributeConverter class [%s] registered multiple times",
							converterClass
					)
			);
		}
	}

	public void addRegistration(RegisteredConversion conversion) {
		if ( registeredConversionsByDomainType == null ) {
			registeredConversionsByDomainType = new ConcurrentHashMap<>();
		}

		final var domainType = getDomainType( conversion );
		checkNotOverriding( conversion, domainType );
		// See if we have a matching entry in attributeConverterDescriptorsByClass.
		// If so, remove it. The conversion being registered will always take precedence.

View on GitHub (pinned to fad1729dce)

Solutions

  1. Register each converter class from exactly one place: either rely on @Converter discovery or register programmatically, not both
  2. Guard programmatic registration with a Set<Class<?>> of already-applied converters (see validation below)
  3. If multiple builders are fed from one config routine, make it idempotent or re-create it per builder

Example fix

// before
metadataBuilder.applyAttributeConverter(MoneyConverter.class);
// ... later, another module also does:
metadataBuilder.applyAttributeConverter(MoneyConverter.class); // throws

// after
private static final Set<Class<? extends AttributeConverter<?,?>>> REGISTERED = ConcurrentHashMap.newKeySet();
if (REGISTERED.add(MoneyConverter.class)) {
    metadataBuilder.applyAttributeConverter(MoneyConverter.class);
}
Defensive patterns

Strategy: validation

Validate before calling

private final Set<Class<? extends AttributeConverter<?,?>>> registered = java.util.concurrent.ConcurrentHashMap.newKeySet();

void registerOnce(MetadataBuilder builder, Class<? extends AttributeConverter<?,?>> clazz) {
    if (registered.add(clazz)) {
        builder.applyAttributeConverter(clazz);
    }
    // silently skip: class already registered via scan or earlier call
}

Try / catch

catch (HibernateException e) {
    if (String.valueOf(e.getMessage()).contains("registered multiple times")) {
        throw new IllegalStateException("Converter registered twice - deduplicate applyAttributeConverter/scanning paths", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling MetadataBuilder.applyAttributeConverter(...) for a class that converter auto-discovery/scanning already registered, registering the same class in two configuration code paths (e.g., both a bootstrap hook and the main builder), or duplicated registration inside a loop over packages/entities.

Common situations: Framework integration code registering converters programmatically while @Converter-annotated classes are also scanned; refactors that moved registration into a shared method called twice; tests building several Metadata instances from a shared routine that accumulates registrations.

Related errors


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