quarkusio/quarkus · error · IllegalArgumentException

Multiple method parameters found that extend 'Throwable'. Wh

Error message

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()}'

What it means

getHandledExceptionTypes deduces which exception type a @ServerExceptionMapper handles when the annotation has no value: it scans method parameters for Throwable subtypes. If two or more parameters are Throwable subtypes, the handled type is ambiguous, so 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:360

    private static Type[] getHandledExceptionTypes(MethodInfo targetMethod) {
        AnnotationValue annotationValue = targetMethod.annotation(SERVER_EXCEPTION_MAPPER).value();
        // handle the case where 'value' is set
        if (annotationValue != null) {
            Type[] valueArray = annotationValue.asClassArray();
            if ((valueArray != null) && (valueArray.length > 0)) {
                return valueArray;
            }
        }

        // 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<>();

View on GitHub (pinned to e1c734241f)

Solutions

  1. Keep exactly one Throwable parameter and remove the other
  2. Handle additional exception types with separate @ServerExceptionMapper methods, each with one Throwable parameter
  3. Specify the handled exception type explicitly via @ServerExceptionMapper(SomeException.class) if extra Throwable-typed data must be passed

Example fix

// before
@ServerExceptionMapper
public Response map(IllegalArgumentException e, IllegalStateException cause) {...}
// after
@ServerExceptionMapper
public Response map(IllegalArgumentException e) {...}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure each value-less @ServerExceptionMapper method has at most one Throwable param
for (Method m : mapperMethods) {
  long count = java.util.Arrays.stream(m.getParameterTypes())
      .filter(Throwable.class::isAssignableFrom).count();
  if (count > 1) throw new IllegalStateException(m + " has " + count + " Throwable parameters");
}

Type guard

boolean hasSingleThrowableParam(Method m) {
  return java.util.Arrays.stream(m.getParameterTypes())
    .filter(Throwable.class::isAssignableFrom).count() == 1;
}

Prevention

When it happens

Trigger: A @ServerExceptionMapper-annotated method without an explicit annotation value declares two parameters that both extend Throwable (e.g. (RuntimeException, MyError)).

Common situations: Adding an extra context-like exception parameter by mistake; combining the mapped exception with a fallback Throwable parameter; copy-paste from a multi-catch handler.

Related errors


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