quarkusio/quarkus · error · IllegalArgumentException

When '@ServerExceptionMapper' is used without a value, then

Error message

When '@ServerExceptionMapper' is used without a value, then the annotated method must contain a method parameter that extends 'Throwable'. Offending method is '${targetMethod.name()}' of class '${targetMethod.declaringClass().name().toString()}'

What it means

When @ServerExceptionMapper is used without a value, RESTEasy Reactive must infer the handled exception from a Throwable-typed method parameter. If no parameter extends Throwable, the handled exception type cannot be determined and an IllegalArgumentException is thrown at build time.

Source

Thrown at independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java:370

        // handle the case where we deduce the type of exception handler by the Throwable defined in method parameters
        Type deducedHandledExceptionType = null;
        List<Type> methodParameters = targetMethod.parameterTypes();
        for (Type methodParameter : methodParameters) {
            if (methodParameter.kind() == Type.Kind.CLASS) {
                Class<?> methodParameterClass = getClassByName(methodParameter.name().toString());
                if (methodParameterClass != null && Throwable.class.isAssignableFrom(methodParameterClass)) {
                    if (deducedHandledExceptionType != null) {
                        throw new IllegalArgumentException(
                                "Multiple method parameters found that extend 'Throwable'. When using '@ServerExceptionMapper', only one parameter can be of type 'Throwable'. Offending method is '"
                                        + targetMethod.name() + "' of class '"
                                        + targetMethod.declaringClass().name().toString() + "'");
                    }
                    deducedHandledExceptionType = methodParameter;
                }
            }
        }
        if (deducedHandledExceptionType == null) {
            throw new IllegalArgumentException(
                    "When '@ServerExceptionMapper' is used without a value, then the annotated method must contain a method parameter that extends 'Throwable'. Offending method is '"
                            + targetMethod.name() + "' of class '" + targetMethod.declaringClass().name().toString() + "'");
        }
        return new Type[] { deducedHandledExceptionType };
    }

    private static Set<String> getCommonHierarchyOfExceptions(Type[] handledExceptions) {
        Set<String> commonHierarchy = new HashSet<>();
        boolean first = true;
        for (Type handledException : handledExceptions) {
            Class<?> handledExceptionClass = getClassByName(handledException.name().toString());
            while (handledExceptionClass != null && !handledExceptionClass.equals(Throwable.class)) {
                String handledExceptionClassName = handledExceptionClass.getName();
                if (first) {
                    commonHierarchy.add(handledExceptionClassName);
                } else if (!commonHierarchy.contains(handledExceptionClassName)) {
                    commonHierarchy.remove(handledExceptionClassName);
                }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Add a parameter that extends Throwable (the exception you want to map)
  2. Or specify the handled type explicitly: @ServerExceptionMapper(NotFoundException.class) and keep a Throwable-compatible signature
  3. Check that the method signature wasn't accidentally changed during refactoring

Example fix

// before
@ServerExceptionMapper
public Response map(UriInfo uriInfo) {...}
// after
@ServerExceptionMapper
public Response map(NotFoundException e, UriInfo uriInfo) {...}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure value-less @ServerExceptionMapper methods declare a Throwable param
for (Method m : mapperMethods) {
  boolean hasThrowable = java.util.Arrays.stream(m.getParameterTypes())
      .anyMatch(Throwable.class::isAssignableFrom);
  if (!hasThrowable) throw new IllegalStateException(m + " needs a Throwable parameter");
}

Type guard

boolean declaresThrowableParam(Method m) {
  return java.util.Arrays.stream(m.getParameterTypes())
    .anyMatch(Throwable.class::isAssignableFrom);
}

Prevention

When it happens

Trigger: @ServerExceptionMapper (no value) applied to a method whose parameters are all non-Throwable types (e.g. only UriInfo, HttpHeaders, or nothing).

Common situations: Forgetting to declare the exception parameter; adding @ServerExceptionMapper to a helper method that takes only request context objects; renaming/refactoring away the exception parameter.

Related errors


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