{"record":{"id":"dcba5c398bdf0391","repo":"quarkusio/quarkus","slug":"multiple-method-parameters-found-that-extend-thro","errorCode":null,"errorMessage":"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()}'","messagePattern":"Multiple method parameters found that extend 'Throwable'\\. When using '@ServerExceptionMapper', only one parameter can be of type 'Throwable'\\. Offending method is '(.+?)' of class '(.+?)'","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java","lineNumber":360,"sourceCode":"    private static Type[] getHandledExceptionTypes(MethodInfo targetMethod) {\n        AnnotationValue annotationValue = targetMethod.annotation(SERVER_EXCEPTION_MAPPER).value();\n        // handle the case where 'value' is set\n        if (annotationValue != null) {\n            Type[] valueArray = annotationValue.asClassArray();\n            if ((valueArray != null) && (valueArray.length > 0)) {\n                return valueArray;\n            }\n        }\n\n        // handle the case where we deduce the type of exception handler by the Throwable defined in method parameters\n        Type deducedHandledExceptionType = null;\n        List<Type> methodParameters = targetMethod.parameterTypes();\n        for (Type methodParameter : methodParameters) {\n            if (methodParameter.kind() == Type.Kind.CLASS) {\n                Class<?> methodParameterClass = getClassByName(methodParameter.name().toString());\n                if (methodParameterClass != null && Throwable.class.isAssignableFrom(methodParameterClass)) {\n                    if (deducedHandledExceptionType != null) {\n                        throw new IllegalArgumentException(\n                                \"Multiple method parameters found that extend 'Throwable'. When using '@ServerExceptionMapper', only one parameter can be of type 'Throwable'. Offending method is '\"\n                                        + targetMethod.name() + \"' of class '\"\n                                        + targetMethod.declaringClass().name().toString() + \"'\");\n                    }\n                    deducedHandledExceptionType = methodParameter;\n                }\n            }\n        }\n        if (deducedHandledExceptionType == null) {\n            throw new IllegalArgumentException(\n                    \"When '@ServerExceptionMapper' is used without a value, then the annotated method must contain a method parameter that extends 'Throwable'. Offending method is '\"\n                            + targetMethod.name() + \"' of class '\" + targetMethod.declaringClass().name().toString() + \"'\");\n        }\n        return new Type[] { deducedHandledExceptionType };\n    }\n\n    private static Set<String> getCommonHierarchyOfExceptions(Type[] handledExceptions) {\n        Set<String> commonHierarchy = new HashSet<>();","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java#L342-L378","documentation":"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.","triggerScenarios":"A @ServerExceptionMapper-annotated method without an explicit annotation value declares two parameters that both extend Throwable (e.g. (RuntimeException, MyError)).","commonSituations":"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.","solutions":["Keep exactly one Throwable parameter and remove the other","Handle additional exception types with separate @ServerExceptionMapper methods, each with one Throwable parameter","Specify the handled exception type explicitly via @ServerExceptionMapper(SomeException.class) if extra Throwable-typed data must be passed"],"exampleFix":"// before\n@ServerExceptionMapper\npublic Response map(IllegalArgumentException e, IllegalStateException cause) {...}\n// after\n@ServerExceptionMapper\npublic Response map(IllegalArgumentException e) {...}","handlingStrategy":"validation","validationCode":"// Ensure each value-less @ServerExceptionMapper method has at most one Throwable param\nfor (Method m : mapperMethods) {\n  long count = java.util.Arrays.stream(m.getParameterTypes())\n      .filter(Throwable.class::isAssignableFrom).count();\n  if (count > 1) throw new IllegalStateException(m + \" has \" + count + \" Throwable parameters\");\n}","typeGuard":"boolean hasSingleThrowableParam(Method m) {\n  return java.util.Arrays.stream(m.getParameterTypes())\n    .filter(Throwable.class::isAssignableFrom).count() == 1;\n}","tryCatchPattern":null,"preventionTips":["One exception parameter per mapper method","Split multi-exception handling into separate mapper methods","Use instanceof checks inside a single Throwable-parameter mapper instead of multiple exception params","Review mapper signatures when merging handlers"],"tags":["quarkus","resteasy-reactive","serverexceptionmapper","build-time"],"backgroundTag":"exception-mapper-parameter-validation","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}