quarkusio/quarkus · error · IllegalStateException

Multiple instances of %1$s were found for Hibernate Search i

Error message

Multiple instances of %1$s were found for Hibernate Search in persistence unit %2$s. At most one instance can be assigned to each persistence unit. Instances found: %3$s

What it means

The persistence-unit-level fallback of the same ambiguity check in HibernateSearchBeanUtil.singleExtensionBeanReferenceFor: with both indexName and backendName null, the extension requests a single bean of the given type for the whole persistence unit (e.g. an ElasticsearchMapper or mass indexer customization). When two or more matching beans exist, at most one may be assigned per persistence unit, so it throws IllegalStateException listing the candidate classes.

Source

Thrown at extensions/hibernate-search-orm-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/orm/elasticsearch/runtime/bean/HibernateSearchBeanUtil.java:44

    private static <T> Optional<BeanReference<T>> singleExtensionBeanReferenceFor(Class<T> beanType,
            String persistenceUnitName, String backendName, String indexName) {
        InjectableInstance<T> instance = extensionInstanceFor(beanType, persistenceUnitName, backendName, indexName);
        if (instance.isAmbiguous()) {
            List<String> ambiguousClassNames = instance.handlesStream().map(h -> h.getBean().getBeanClass().getCanonicalName())
                    .toList();
            if (indexName != null) {
                throw new IllegalStateException(String.format(Locale.ROOT,
                        "Multiple instances of %1$s were found for Hibernate Search index %2$s in persistence unit %3$s."
                                + " At most one instance can be assigned to each index. Instances found: %4$s",
                        beanType.getSimpleName(), indexName, persistenceUnitName, ambiguousClassNames));
            } else if (backendName != null) {
                throw new IllegalStateException(String.format(Locale.ROOT,
                        "Multiple instances of %1$s were found for Hibernate Search backend %2$s in persistence unit %3$s."
                                + " At most one instance can be assigned to each backend. Instances found: %4$s",
                        beanType.getSimpleName(), backendName, persistenceUnitName, ambiguousClassNames));
            } else {
                throw new IllegalStateException(String.format(Locale.ROOT,
                        "Multiple instances of %1$s were found for Hibernate Search in persistence unit %2$s."
                                + " At most one instance can be assigned to each persistence unit. Instances found: %3$s",
                        beanType.getSimpleName(), persistenceUnitName, ambiguousClassNames));
            }
        }
        return instance.isResolvable() ? Optional.of(new ArcBeanReference<>(instance.getHandle().getBean())) : Optional.empty();
    }

    public static <T> Optional<List<BeanReference<T>>> multiExtensionBeanReferencesFor(Optional<List<String>> override,
            Class<T> beanType,
            String persistenceUnitName, String backendName, String indexName) {
        return override.map(strings -> strings.stream()
                .map(string -> BeanReference.parse(beanType, string))
                .collect(Collectors.toList()))
                .or(() -> multiExtensionBeanReferencesFor(beanType, persistenceUnitName, backendName, indexName));
    }

    private static <T> Optional<List<BeanReference<T>>> multiExtensionBeanReferencesFor(Class<T> beanType,

View on GitHub (pinned to e1c734241f)

Solutions

  1. Keep only one bean of that type without backend/index qualifiers; delete the duplicate shown in the message.
  2. Target each bean more precisely with backend and/or index qualifiers in @SearchExtension so the persistence-unit lookup becomes unique.
  3. Use @Alternative + @Priority on your override instead of adding a second bean alongside the @DefaultBean.
  4. If both beans are legitimately needed, merge their behavior into a single bean implementing the SPI.

Example fix

// before
@SearchExtension
public class CustomizerA implements ElasticsearchMappingConfigurer { ... }
@SearchExtension
public class CustomizerB implements ElasticsearchMappingConfigurer { ... }

// after: merged into one bean
@SearchExtension
public class CustomizerA implements ElasticsearchMappingConfigurer { ... } // B deleted
Defensive patterns

Strategy: validation

Validate before calling

// Ensure only one persistence-unit-wide bean of the customizer type exists:
List<String> beans = Arc.container().instance(ElasticsearchMappingConfigurer.class)
    .handlesStream().map(h -> h.getBean().getBeanClass().getName()).toList();
if (beans.size() > 1) {
    throw new IllegalStateException("Merge these into one bean: " + beans);
}

Try / catch

try {
    bootStandaloneSearch();
} catch (IllegalStateException e) {
    if (e.getMessage() != null && e.getMessage().contains("per persistence unit")) {
        LOGGER.error("Duplicated persistence-unit-wide @SearchExtension bean: " + e.getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: singleExtensionBeanReferenceFor called with indexName == null and backendName == null; the InjectableInstance built from beans carrying the bare @SearchExtension qualifier (no backend/index) is ambiguous, so the final else-branch formats this message and throws.

Common situations: Declaring two persistence-unit-wide customizations of the same Hibernate Search SPI type (e.g. two mapper-level bean customizers) via @SearchExtension with no target attributes; a leftover bean from a refactor duplicating an existing one; test beans leaking into the production archive via wrong package placement.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/032257a7e3b29029. Report an issue: GitHub.