quarkusio/quarkus · error · IllegalStateException

Only a single instance of '@ClientExceptionMapper' is allowe

Error message

Only a single instance of '@ClientExceptionMapper' is allowed per REST Client interface. Offending class is '${class}'

What it means

A REST Client interface may declare at most one method annotated with `@ClientExceptionMapper` (including inherited ones). Quarkus generates one exception-mapper class per interface during build and throws IllegalStateException when a second instance targets the same interface.

Source

Thrown at extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java:878

            BuildProducer<ExecutionModelAnnotationsAllowedBuildItem> executionModelAnnotationsAllowedProducer) {

        executionModelAnnotationsAllowedProducer.produce(new ExecutionModelAnnotationsAllowedBuildItem(
                new Predicate<>() {
                    @Override
                    public boolean test(MethodInfo methodInfo) {
                        return methodInfo.hasDeclaredAnnotation(CLIENT_EXCEPTION_MAPPER);
                    }
                }));

        var result = new HashMap<String, GeneratedClassResult>();
        ClientExceptionMapperHandler clientExceptionMapperHandler = new ClientExceptionMapperHandler(gizmo);
        for (AnnotationInstance instance : index.getAnnotations(CLIENT_EXCEPTION_MAPPER)) {
            GeneratedClassResult classResult = clientExceptionMapperHandler.generateResponseExceptionMapper(instance);
            if (classResult == null) {
                continue;
            }
            if (result.containsKey(classResult.interfaceName)) {
                throw new IllegalStateException("Only a single instance of '" + CLIENT_EXCEPTION_MAPPER
                        + "' is allowed per REST Client interface. Offending class is '" + classResult.interfaceName + "'");
            }
            result.put(classResult.interfaceName, classResult);
            reflectiveClassesProducer.produce(ReflectiveClassBuildItem.builder(classResult.generatedClassName)
                    .reason(getClass().getName())
                    .build());
        }
        return result;
    }

    private Map<String, GeneratedClassResult> populateClientRedirectHandlerFromAnnotations(
            Gizmo gizmo,
            BuildProducer<ReflectiveClassBuildItem> reflectiveClasses, IndexView index) {

        var result = new HashMap<String, GeneratedClassResult>();
        ClientRedirectHandler clientHandler = new ClientRedirectHandler(gizmo);
        for (AnnotationInstance instance : index.getAnnotations(CLIENT_REDIRECT_HANDLER)) {
            GeneratedClassResult classResult = clientHandler.generateResponseExceptionMapper(instance);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Keep only one `@ClientExceptionMapper` method per interface and merge the logic
  2. Move shared mapping logic into a helper method called by the single mapper
  3. Check parent interfaces for inherited mappers and remove duplicates

Example fix

// before
@ClientExceptionMapper Response map1(Response r) {...}
@ClientExceptionMapper Response map2(Response r) {...}
// after
@ClientExceptionMapper Response map(Response r) {
    if (r.getStatus()==404) return notFound();
    return map2Logic(r);
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify at build/test time only one mapper exists per interface
long count = Arrays.stream(MyClient.class.getMethods())
    .filter(m -> m.isAnnotationPresent(ClientExceptionMapper.class))
    .count();
if (count > 1) throw new IllegalStateException("Duplicate @ClientExceptionMapper on " + MyClient.class);

Prevention

When it happens

Trigger: Two `@ClientExceptionMapper` static methods defined on the same client interface (possibly via interface inheritance or a shared parent interface).

Common situations: Adding a new mapper while an inherited default one already exists; multiple interfaces extending a common interface each defining their own mapper and then both applied to one client.

Related errors


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