Repository: quarkusio/quarkus
Issue: @ServerExceptionMapper doesn't support multiple exception types

### Describe the bug

```
@ServerExceptionMapper(value = {MyExceptionOne.class, MyExceptionTwo.class})
    public Uni<Response> mapException(RuntimeException e) {
        return Uni.createFrom().item(() ->  Response.status(Response.Status.CONFLICT)
                .entity(Map.of(MESSAGE, message))
                .build(););
    }
```

### Expected behavior

multiple exception classes are supported

### Actual behavior

```
java.lang.RuntimeException: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
	[error]: Build step io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor#handleCustomAnnotatedMethods threw an exception: java.lang.RuntimeException: Parameter 'e' of method 'mapException of class '...' is not allowed

```

### How to Reproduce?

_No response_

### Output of `uname -a` or `ver`

Darwin RDU-W4HVVTY7VT 21.4.0 Darwin Kernel Version 21.4.0

### Output of `java -version`

openjdk version "17.0.5" 2022-10-18 OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08) OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)

### GraalVM version (if different from Java)

openjdk version "17.0.5" 2022-10-18 OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08) OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)

### Quarkus version or git rev

2.16.1.Final

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

maven

### Additional information

I see that this issue was fixed in https://github.com/quarkusio/quarkus/issues/20202, but it appeared again

Changed files:
independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java
extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/UniExceptionMapper.java

diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/UniExceptionMapper.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/UniExceptionMapper.java
index ee5f1e771f5..d86e45824db 100644
--- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/UniExceptionMapper.java
+++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customexceptions/UniExceptionMapper.java
@@ -9,7 +9,7 @@
 public class UniExceptionMapper {
 
     @ServerExceptionMapper({ UniException.class, OtherUniException.class })
-    Uni<Response> handleUni(Throwable t) {
+    Uni<Response> handleUni(UniException t) {
         return Uni.createFrom().deferred(() -> Uni.createFrom().item(Response.status(413).build()));
     }
 
diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java
index 878d38bfada..2782c62bcbe 100644
--- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java
+++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/exceptionmappers/ServerExceptionMapperGenerator.java
@@ -18,8 +18,8 @@
 import static org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames.SIMPLIFIED_RESOURCE_INFO;
 
 import java.lang.reflect.Modifier;
-import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -123,7 +123,7 @@ public static Map<String, String> generatePerClassMapper(MethodInfo targetMethod
         Map<String, String> result = new HashMap<>();
 
         boolean handlesMultipleExceptions = handledExceptionTypes.length > 1;
-        boolean handledContainsThrowable = Arrays.stream(handledExceptionTypes).anyMatch(t -> t.name().equals(THROWABLE));
+        Set<String> commonHierarchyOfExceptions = getCommonHierarchyOfExceptions(handledExceptionTypes);
 
         for (Type handledExceptionType : handledExceptionTypes) {
             String generatedClassName = getGeneratedClassName(targetMethod, handledExceptionType);
@@ -155,7 +155,7 @@ public static Map<String, String> generatePerClassMapper(MethodInfo targetMethod
 
                     // RESTEasy Reactive toResponse(...) method
                     generateRRResponse(targetMethod, targetClass, handledExceptionType, cc, rrToResponseDescriptor,
-                            returnType, handlesMultipleExceptions, handledContainsThrowable,
+                            returnType, handlesMultipleExceptions, commonHierarchyOfExceptions,
                             (method, contextHandle) -> {
                                 ResultHandle endpointInstanceHandle = method.invokeVirtualMethod(
                                         ofMethod(ResteasyReactiveRequestContext.class, "getEndpointInstance", Object.class),
@@ -173,7 +173,7 @@ public static Map<String, String> generatePerClassMapper(MethodInfo targetMethod
 
                     // RESTEasy Reactive asyncResponse(...) method
                     generateRRUniResponse(targetMethod, targetClass, handledExceptionType, cc, rrAsyncResponseDescriptor,
-                            returnType, handlesMultipleExceptions, handledContainsThrowable,
+                            returnType, handlesMultipleExceptions, commonHierarchyOfExceptions,
                             (method, contextHandle) -> {
                                 ResultHandle endpointInstanceHandle = method.invokeVirtualMethod(
                                         ofMethod(ResteasyReactiveRequestContext.class, "getEndpointInstance", Object.class),
@@ -251,7 +251,7 @@ public static Map<String, String> generateGlobalMapper(MethodInfo targetMethod,
         Map<String, String> result = new HashMap<>();
 
         boolean handlesMultipleExceptions = handledExceptionTypes.length > 1;
-        boolean handledContainsThrowable = Arrays.stream(handledExceptionTypes).anyMatch(t -> t.name().equals(THROWABLE));
+        Set<String> commonHierarchyOfExceptions = getCommonHierarchyOfExceptions(handledExceptionTypes);
 
         for (Type handledExceptionType : handledExceptionTypes) {
             String generatedClassName = getGeneratedClassName(targetMethod, handledExceptionType);
@@ -311,7 +311,7 @@ public static Map<String, String> generateGlobalMapper(MethodInfo targetMethod,
 
                     // RESTEasy Reactive toResponse(...) method
                     generateRRResponse(targetMethod, targetClass, handledExceptionType, cc, rrToResponseDescriptor,
-                            returnType, handlesMultipleExceptions, handledContainsThrowable,
+                            returnType, handlesMultipleExceptions, commonHierarchyOfExceptions,
                             (method, contextHandle) -> method.readInstanceField(delegateField, method.getThis()),
                             unwrappableTypes);
                 } else if (returnType == ReturnType.UNI_RESPONSE || returnType == ReturnType.UNI_REST_RESPONSE) {
@@ -325,7 +325,7 @@ public static Map<String, String> generateGlobalMapper(MethodInfo targetMethod,
 
                     // RESTEasy Reactive asyncResponse(...) method
                     generateRRUniResponse(targetMethod, targetClass, handledExceptionType, cc, rrAsyncResponseDescriptor,
-                            returnType, handlesMultipleExceptions, handledContainsThrowable,
+                            returnType, handlesMultipleExceptions, commonHierarchyOfExceptions,
                             (method, contextHandle) -> method.readInstanceField(delegateField, method.getThis()),
                             unwrappableTypes);
                 } else {
@@ -352,20 +352,15 @@ private static Type[] getHandledExceptionTypes(MethodInfo targetMethod) {
         List<Type> methodParameters = targetMethod.parameterTypes();
         for (Type methodParameter : methodParameters) {
             if (methodParameter.kind() == Type.Kind.CLASS) {
-                try {
-                    Class<?> methodParameterClass = Class.forName(methodParameter.name().toString(), false,
-                            Thread.currentThread().getContextClassLoader());
-                    if (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;
+                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() + "'");
                     }
-                } catch (ClassNotFoundException ignored) {
-
+                    deducedHandledExceptionType = methodParameter;
                 }
             }
         }
@@ -377,6 +372,36 @@ private static Type[] getHandledExceptionTypes(MethodInfo targetMethod) {
         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);
+                }
+
+                handledExceptionClass = handledExceptionClass.getSuperclass();
+            }
+
+            first = false;
+        }
+
+        return commonHierarchy;
+    }
+
+    private static Class<?> getClassByName(String className) {
+        try {
+            return Class.forName(className, false, Thread.currentThread().getContextClassLoader());
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
     private static MethodDescriptor toResponseDescriptor(Type handledExceptionType, String generatedClassName) {
         return ofMethod(generatedClassName, "toResponse",
                 Response.class.getName(),
@@ -432,7 +457,7 @@ private static void generateRRResponseBridge(Type handledExceptionType, ClassCre
 
     private static void generateRRResponse(MethodInfo targetMethod, ClassInfo targetClass, Type handledExceptionType,
             ClassCreator cc, MethodDescriptor rrToResponseDescriptor,
-            ReturnType returnType, boolean handlesMultipleExceptions, boolean handledContainsThrowable,
+            ReturnType returnType, boolean handlesMultipleExceptions, Set<String> commonHierarchyOfExceptions,
             BiFunction<MethodCreator, ResultHandle, ResultHandle> targetInstanceHandleCreator, Set<DotName> unwrappableTypes) {
         MethodCreator mc = cc.getMethodCreator(rrToResponseDescriptor);
         ResultHandle exceptionHandle = mc.getMethodParam(0);
@@ -452,7 +477,7 @@ private static void generateRRResponse(MethodInfo targetMethod, ClassInfo target
         } else {
             TargetMethodParamsInfo targetMethodParamsInfo = getTargetMethodParamsInfo(targetMethod, targetClass,
                     handledExceptionType, mc, exceptionHandle, contextHandle, handlesMultipleExceptions,
-                    handledContainsThrowable, unwrappableTypes);
+                    commonHierarchyOfExceptions, unwrappableTypes);
             ResultHandle resultHandle = mc.invokeVirtualMethod(
                     ofMethod(targetClass.name().toString(), targetMethod.name(),
                             targetMethod.returnType().name().toString(), targetMethodParamsInfo.getTypes()),
@@ -479,7 +504,7 @@ private static void generateRRUniResponseBridge(Type handledExceptionType, Class
 
     private static void generateRRUniResponse(MethodInfo targetMethod, ClassInfo targetClass, Type handledExceptionType,
             ClassCreator cc, MethodDescriptor rrAsyncResponseDescriptor,
-            ReturnType returnType, boolean handlesMultipleExceptions, boolean handledContainsThrowable,
+            ReturnType returnType, boolean handlesMultipleExceptions, Set<String> commonHierarchyOfExceptions,
             BiFunction<MethodCreator, ResultHandle, ResultHandle> targetInstanceHandleCreator, Set<DotName> unwrappableTypes) {
         MethodCreator mc = cc.getMethodCreator(rrAsyncResponseDescriptor);
         ResultHandle exceptionHandle = mc.getMethodParam(0);
@@ -498,7 +523,7 @@ private static void generateRRUniResponse(MethodInfo targetMethod, ClassInfo tar
         } else {
             TargetMethodParamsInfo targetMethodParamsInfo = getTargetMethodParamsInfo(targetMethod, targetClass,
                     handledExceptionType, mc, exceptionHandle, contextHandle, handlesMultipleExceptions,
-                    handledContainsThrowable, unwrappableTypes);
+                    commonHierarchyOfExceptions, unwrappableTypes);
             uniHandle = mc.invokeVirtualMethod(
                     ofMethod(targetClass.name().toString(), targetMethod.name(),
                             Uni.class.getName(), targetMethodParamsInfo.getTypes()),
@@ -512,7 +537,7 @@ private static void generateRRUniResponse(MethodInfo targetMethod, ClassInfo tar
 
     private static TargetMethodParamsInfo getTargetMethodParamsInfo(MethodInfo targetMethod, ClassInfo targetClass,
             Type handledExceptionType, MethodCreator mc, ResultHandle exceptionHandle, ResultHandle contextHandle,
-            boolean handlesMultipleExceptions, boolean handledContainsThrowable, Set<DotName> unwrappableTypes) {
+            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()];
@@ -522,39 +547,19 @@ private static TargetMethodParamsInfo getTargetMethodParamsInfo(MethodInfo targe
             DotName paramDotName = parameter.name();
             parameterTypes[i] = paramDotName.toString();
             String parameterName = targetMethod.parameterName(i);
-            if (paramDotName.equals(handledExceptionType.name())) {
-                if (handlesMultipleExceptions) {
+            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 '"
-                            + (handledContainsThrowable ? Throwable.class.getName() : Exception.class.getName())
-                            + "' instead.");
+                            + Throwable.class.getName() + "' instead.");
                 } else {
                     targetMethodParamHandles[i] = exceptionHandle;
                 }
-            } else if (paramDotName.equals(THROWABLE)) {
+            } else if (commonHierarchyOfExceptions.contains(paramDotName.toString())) {
                 targetMethodParamHandles[i] = exceptionHandle;
-            } else if (paramDotName.equals(EXCEPTION)) {
-                if (handlesMultipleExceptions) {
-                    if (handledContainsThrowable) {
-                        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 '" + Exception.class.getName()
-                                + "' instead.");
-                    } else {
-                        targetMethodParamHandles[i] = exceptionHandle;
-                    }
-                } else {
-                    if (handledContainsThrowable) {
-                        throw new RuntimeException("Parameter '" + parameterName + "' of method '" + targetMethod.name()
-                                + " of class '" + targetClass.name()
-                                + "' cannot be of type '" + handledExceptionType.name() + "'. You can use '"
-                                + Throwable.class.getName() + "' instead.");
-                    } else {
-                        targetMethodParamHandles[i] = exceptionHandle;
-                    }
-                }
             } else if (paramDotName.equals(handledExceptionType.name())) {
                 targetMethodParamHandles[i] = exceptionHandle;
             } else if (CONTAINER_REQUEST_CONTEXT.equals(paramDotName)