4

I've got an @Aspect annotated class that is calling ProceedingJoinPoint#proceed(). This method throws Throwable and thus the class looks something like this:

@Aspect
@Component
public class MyClass{

    @Around("@annotation(myAnnotation)")
    public Object myMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
        //some code here
        try {
            return joinPoint.proceed();
        } finally {
            //some more code here
        }
    }
}

Is it ok for myMehtod to throw a Throwable in this scenario where I have to call another method that throws Throwable? Should I avoid throwing Throwable and somehow convert it to Exception or Error?

In either case I'd like to know why as well. Thank you.

Community
  • 1
  • 1
João Neves
  • 944
  • 1
  • 13
  • 18

2 Answers2

5

No, it is not okay to throw Throwable. Errors and unchecked exceptions are not supposed to be caught; Errors are serious or fatal system problems, while unchecked exceptions (usually) expose programmer mistakes. Declaring a signature with throws Throwable forces callers to catch, and allows them to suppress, things which shouldn't be caught or suppressed.

If at all possible, you should fix the ProceedingJoinPoint.proceed() method's signature, but if you don't have control over that class, your own code should wrap it in a more reasonable exception.

How to wrap it depends on what you expect of callers of your code. If any Throwable is likely to be a condition that no one can possibly do anything to correct or address, you might as well wrap it in an unchecked RuntimeException:

try {
    return joinPoint.proceed();
} catch (Throwable t) {
    throw new RuntimeException(t);
} finally {
    //some more code here
}

If you want to make sure callers handle all Throwables gracefully, you should create your own application-specific exception class:

try {
    return joinPoint.proceed();
} catch (Throwable t) {
    throw new JoinPointException(t);
} finally {
    //some more code here
}

The exception class is simply:

public class JoinPointException
extends Exception {
    private static final long serialVersionUID = 1;

    public JoinPointException(Throwable cause) {
        super(cause);
    }

    public JoinPointException(String message,
                              Throwable cause) {
        super(message, cause);
    }
}
VGR
  • 40,506
  • 4
  • 48
  • 63
  • Thank you for the answer! The `ProceedingJoinPoint#proceed()` is part of the [AspectJ Project](https://eclipse.org/aspectj/) so I can't change that method's signature. – João Neves Feb 10 '16 at 17:28
  • @VGR - Pesky comment - declaring a throwable does not "allow to suppress". Unfortunately you are always allowed to suppress any type of exception. – Amit Goldstein Jun 04 '19 at 08:51
0

If you use Lombok you can use @SneakyThrows in order to rethrow a Throwable without declaring it.

@Aspect
@Component
public class MyClass{

    @Around("@annotation(myAnnotation)")
    public Object myMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
        //some code here
        try {
            return proceed(joinPoint)
        } finally {
            //some more code here
        }
    }

    @SneakyThrows
    public Object proceed(ProceedingJoinPoint joinPoint) {
        return joinPoint.proceed();
    }
}

This is the only way I know to avoid both catching and wrapping a Throwable or a checked exception.

Amit Goldstein
  • 827
  • 9
  • 18
  • Adding to @AmitGoldstein 's answer- This trick will help us in situations we don't want to wrap a checked exception in e.g. a `Runnable` or don't want to propagate a non-useful checked exception e.g. `UnsupportedEncodingException` to the callers. But this is not a golden hammer to replace the `throws` clause. – Calvin May 28 '22 at 14:10