quarkusio/quarkus · error · RuntimeException

Parameter '${parameterName}' of method '${targetMethod.name(

Error message

Parameter '${parameterName}' of method '${targetMethod.name()} of class '${targetClass.name()}' cannot be of type '${handledExceptionType.name()}' because the method handles multiple exceptions. You can use 'java.lang.Throwable' instead.

What it means

When a @ServerExceptionMapper handles multiple exception types (a common hierarchy was deduced for several @ServerExceptionMapper values), generated code passes the caught Throwable as the mapped exception parameter. A parameter typed as a SPECIFIC exception subtype cannot be safely used unless it is part of the common hierarchy of all handled exceptions, so a RuntimeException 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:556

    }

    private static TargetMethodParamsInfo getTargetMethodParamsInfo(MethodInfo targetMethod, ClassInfo targetClass,
            Type handledExceptionType, MethodCreator mc, ResultHandle exceptionHandle, ResultHandle contextHandle,
            boolean handlesMultipleExceptions, Set<String> commonHierarchyOfExceptions, Set<DotName> unwrappableTypes) {
        List<Type> parameters = targetMethod.parameterTypes();
        ResultHandle[] targetMethodParamHandles = new ResultHandle[parameters.size()];
        String[] parameterTypes = new String[parameters.size()];
        // TODO: we probably want to refactor this and remove duplicate code that also exists in CustomFilterGenerator
        for (int i = 0; i < parameters.size(); i++) {
            Type parameter = parameters.get(i);
            DotName paramDotName = parameter.name();
            parameterTypes[i] = paramDotName.toString();
            String parameterName = targetMethod.parameterName(i);
            if (paramDotName.equals(THROWABLE)) {
                targetMethodParamHandles[i] = exceptionHandle;
            } else if (paramDotName.equals(handledExceptionType.name())) {
                if (handlesMultipleExceptions && !commonHierarchyOfExceptions.contains(paramDotName.toString())) {
                    throw new RuntimeException("Parameter '" + parameterName + "' of method '" + targetMethod.name()
                            + " of class '" + targetClass.name() + "' cannot be of type '" + handledExceptionType.name()
                            + "' because the method handles multiple exceptions. You can use '"
                            + Throwable.class.getName() + "' instead.");
                } else {
                    targetMethodParamHandles[i] = exceptionHandle;
                }
            } else if (commonHierarchyOfExceptions.contains(paramDotName.toString())) {
                targetMethodParamHandles[i] = exceptionHandle;
            } else if (paramDotName.equals(handledExceptionType.name())) {
                targetMethodParamHandles[i] = exceptionHandle;
            } else if (CONTAINER_REQUEST_CONTEXT.equals(paramDotName)
                    || QUARKUS_REST_CONTAINER_REQUEST_CONTEXT.equals(paramDotName)) {
                targetMethodParamHandles[i] = mc.invokeVirtualMethod(
                        ofMethod(ResteasyReactiveRequestContext.class.getName(), "getContainerRequestContext",
                                ContainerRequestContextImpl.class),
                        contextHandle);
            } else if (SERVER_REQUEST_CONTEXT.equals(paramDotName)) {
                targetMethodParamHandles[i] = contextHandle;

View on GitHub (pinned to e1c734241f)

Solutions

  1. Change the exception parameter type to java.lang.Throwable and instanceof-cast inside the method
  2. Remove the multiple exception values and create one @ServerExceptionMapper method per specific exception type
  3. Ensure the parameter type is a common ancestor of all listed exception types so it appears in the deduced hierarchy

Example fix

// before
@ServerExceptionMapper({NotFoundException.class, BadRequestException.class})
public Response map(NotFoundException e) {...}
// after
@ServerExceptionMapper({NotFoundException.class, BadRequestException.class})
public Response map(Throwable e) {...}
Defensive patterns

Strategy: type-guard

Validate before calling

// For multi-exception mappers, require Throwable (or a common ancestor) parameter
for (Method m : multiExceptionMappers) {
  for (Class<?> p : m.getParameterTypes()) {
    if (Throwable.class.isAssignableFrom(p) && p != Throwable.class && !isCommonAncestorOfAllValues(p, m))
      throw new IllegalStateException(m + ": parameter " + p + " must be Throwable or a common ancestor");
  }
}

Type guard

boolean safeMultiExceptionParam(Method m, Class<?> param) {
  if (param == Throwable.class) return true;
  ClassInfo ci = indexedClass(param);
  return commonHierarchyOfExceptions.containsAll(
    java.util.Arrays.stream(m.getAnnotation(ServerExceptionMapper.class).value())
      .map(Class::getName).collect(java.util.stream.Collectors.toSet())
      .stream().filter(n -> !ci.name().toString().equals(n)).toList()) || param == Throwable.class;
}

Try / catch

// In the mapper body, narrow the Throwable safely:
if (e instanceof NotFoundException nfe) { ... } else if (e instanceof BadRequestException bre) { ... }

Prevention

When it happens

Trigger: A mapper method (e.g. @ServerExceptionMapper({A.class, B.class})) where A and B are unrelated and the method has a parameter typed as A (or B, or a subtype) that is not java.lang.Throwable or a member of the deduced common exception hierarchy.

Common situations: Declaring multiple exception types in @ServerExceptionMapper while keeping a specific exception parameter; merging two mappers into one without changing the parameter type; misunderstanding that only Throwable works for multi-exception mappers.

Related errors


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