Uploaded image for project: 'WildFly'
  1. WildFly
  2. WFLY-21469

Contextual proxy wraps runtime exceptions with UndeclaredThrowableException

XMLWordPrintable

      Runtime exceptions thrown within contextual proxies created via jakarta.enterprise.concurrent.ContextService are not propagated directly but are wrapped in an UndeclaredThrowableException. Here are the actions to reproduce this behaviour: 1) build the attached project by executing mvn package 2) deploy the reproducer.war 3) send a get request to

      http://localhost:8080/reproducer/api/test

      4) if the unexpected behaviour occurs you will see the message "unexpected: java.lang.reflect.UndeclaredThrowableException"

      Intial findings:- 
      POSSIBLE ROOT CAUSE

      The Reflection Wrapper: The ContextService uses Java Reflection (method.invoke()) to run code. By design, Java Reflection catches any exception thrown by the underlying method and wraps it inside a Checked Exception called InvocationTargetException.

      The Failure to Unwrap: The EAP 8 proxy implementation catches this InvocationTargetException but fails to "peel it off" (unwrap it) to retrieve your original MyRuntimeException. It throws the wrapper exception instead.

      The Interface Violation: interface MyProxy declares throws MyRuntimeException, but it does not declare throws InvocationTargetException.

      The Final Crash: The Java Dynamic Proxy mechanism sees a Checked Exception (InvocationTargetException) being thrown that is not listed in the interface's throws clause. To adhere to Java rules, it creates an UndeclaredThrowableException to wrap this "illegal" exception and throws that to calling code.

      @GET
      public String test() {
      try

      { return contextService.createContextualProxy(this::contextualTest, MyProxy.class).test(); }

      catch (MyRuntimeException ex) {
      // This catches it if it comes out clean
      return "first expected: " + ex;
      } catch (UndeclaredThrowableException ex) {
      // 1. Get the immediate cause (likely InvocationTargetException)
      Throwable cause = ex.getUndeclaredThrowable();

      // 2. If the cause is a Reflection wrapper, peel it one more time
      if (cause instanceof InvocationTargetException)

      { cause = ((InvocationTargetException) cause).getTargetException(); }

      // 3. Now check if the inner-most cause is YOUR exception
      if (cause instanceof MyRuntimeException) {
      return "expected (unwrapped): " + cause;
      }

      // If it's something else entirely, rethrow or handle as unexpected
      return "unexpected inner cause: " + cause;
      } catch (Exception ex) {
      return "unexpected: " + ex;
      }
      }

              emartins@redhat.com Eduardo Martins
              emartins@redhat.com Eduardo Martins
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated:
                Resolved: