Application of @Sneaky Throws in lombok

38.1k views Asked by At

I was playing with the Lombok library in Java and found an annotation called @SneakyThrows. As the documentation states:

@SneakyThrows fakes out the compiler. In other words, Lombok doesn't wrap or replace the thrown checked exception, but makes the compiler think that it is an unchecked exception.

With other words, this is a way to bypass exceptions at compile time. But in my opinion this should not be the correct way of handling exceptions, because the bypassed exception can show weird behaviour at runtime.

So in which scenario should @SneakyThrows be used?

7

There are 7 answers

1
SnowmanXL On BEST ANSWER

To add to the existing answers. I personally dislike checked exceptions. See for more info: https://phauer.com/2015/checked-exceptions-are-evil/

To add insult to injury, the code gets bloated when avoiding the checked exceptions. Consider the usage of @SneakyThrows:

 List<Instant> instantsSneaky = List.of("2020-09-28T12:30:08.797481Z")
        .stream()
        .map(Example::parseSneaky)
        .collect(Collectors.toList());

@SneakyThrows
private static Instant parseSneaky(String queryValue) {
    return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(queryValue).toInstant();
}

versus non-@SneakyThrows

 private static Instant parseNonSneaky(String queryValue) throws ParseException {
    return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(queryValue).toInstant();
}

List<Instant> instantsNonSneaky = List.of("2020-09-28T12:30:08.797481Z")
        .stream()
        .map(timeStamp -> {
            try {
                return parseNonSneaky(timeStamp);
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
        })
        .collect(Collectors.toList());

Hence the applicance of @SneakyThrows enables much cleaner code.

1
Raghuveer On

In the JAVA 8 and above when using lambda especially its not an easy way to use.

  1. Consider it mostly for older versions of Java 8.
  2. The purpose itself is to throw an exception deliberately for example for warning. By this the other services/program/code can identify how the request/response flow should be handled. If you already have mechanism in place no need to worry about it.

@SneakyThrows is not of much use in current traditional application development, could be used in some kinda state machine programs where it would be necessary (i do not have expertise in it though) to determine the state of the programs current flow. This is just 1 example of different scenarios there maybe more.

3
MarkAddison On

I believe the intention here is to cause the compiler to not require a throws whatever Exception to be added to the method declaration.

For example if the method was

public void throwsCheckedException() {
    throw new IOException("IO exception thrown");
}

This would cause a compile time exception requiring

public void throwsCheckedException() throws IOException {
    throw new IOException("IO exception thrown");
}

The annotation @SneakThrows mitigates this - original method declared as

@SneakyThrows
public void throwsCheckedException() {
    throw new IOException("IO exception thrown");
}

This will not cause a compile time error. Note IDEs might still highlight this as an error, for example in IntelliJ you will need to utilise the Lombok plugin.

0
Ramji On

Normally when we have an interface and there are more than one implementations of the methods declared in the interface we might find it difficult to handle the signature changes for the exceptions thrown in the implementations.

Say we have an interface and 2 implementations.

public interface A {
    void methodSignature(String x);  // This has to be changed owing to the various Exception thrown from the implementations
}

public class B implements A {
    @Override
    void methodSignature(String x) throws IOException {
        // implementation for B
    }
}

public class C implements A {
    @Override
    void methodSignature(String x) throws ApiException {
        // implementation of C
    }
}

We can use a common throws Exception in place of IOException and ApiException but if there are more types of exceptions to be handled it might not be clear.

Hence we use @Sneakythrows and send the exceptions to GlobalException handlers annotated with @ControllerAdvice that would control the flow.

public interface A {
    void methodSignature(String x); 
}

public class B implements A {
    @Override
    @SneakyThrows
    void methodSignature(String x) {
        // implementation for B
    }
}

public class C implements A {
    @Override
    @SneakyThrows
    void methodSignature(String x) {
        // implementation of C
    }
}

This in particular helps when we have a contract with the users of the interfaces finalised.

A sample of global exception handler looks like below

    @ControllerAdvice
    public class GlobalExceptionHandler {
    @ExceptionHandler(
            value = {HttpMessageNotReadableException.class, DataAccessException.class, Exception.class, RuntimeException.class})
        public ResponseEntity defaultWebExceptionHandler(Exception ex, HttpServletRequest servletRequest, HttpServletResponse httpResponse) {
    
            LOGGER.error("A controller threw an exception", ex);
            ResponseEntity returnValue = null;
            ErrorInfo errorInfo = new ErrorInfo();
            try {
                throw ex;
            } catch (ResourceNotFoundException resourceNotFound) {
                errorInfo.setUserMessage(resourceNotFound.getMessage());
                errorInfo.setTechnicalDetails(resourceNotFound.getMessage());
                errorInfo.setMessageCode(HttpStatus.NOT_FOUND.toString());
                returnValue = new ResponseEntity(errorInfo, HttpStatus.NOT_FOUND);
            } catch (HttpMessageNotReadableException hme) {
// ...
            } catch (DataAccessException dae) {
// ...
            } catch (Exception exception) {
// ...
            }
            configureHttpResponse(httpResponse, null, servletRequest.getRequestURI());
            return returnValue;
        }
    }
0
Raul Lapeira Herrero On

We just had an exception, thrown but not shown anywhere, and there was no any empty catch (in the context of java.util.concurrent and TaskManager), took hours to debug... No SneakyThrows for me.

0
Saeed Hassanvand On

I think the documentation is very clear on this:

Common use cases for when you want to opt out of the checked exception mechanism center around 2 situations:

  1. A needlessly strict interface, such as Runnable - whatever exception propagates out of your run() method, checked or not, it will be passed to the Thread's unhandled exception handler. Catching a checked exception and wrapping it in some sort of RuntimeException is only obscuring the real cause of the issue.

  2. An 'impossible' exception. For example, new String(someByteArray, "UTF-8"); declares that it can throw an UnsupportedEncodingException but according to the JVM specification, UTF-8 must always be available. An UnsupportedEncodingException here is about as likely as a ClassNotFoundError when you use a String object, and you don't catch those either!

0
Marian Klühspies On

I regularly use the @SneakyThrows annotation in the following scenarios:

  1. Supress try-catch boilerplate when using lambdas
  2. When a specific exception should terminate the entire process and there is no need to force any caller of the hierarchy to handle it explicitly (although RuntimeException can be used here as well)
  3. Usage of third-party APIs forcing you to handle an exception but you have no need to (goes hand in hand with 2.)