1. Overview
In Java, the sneaky throw concept allows us to throw any checked exception without defining it explicitly in the method signature. This allows the omission of the throws declaration, effectively imitating the characteristics of a runtime exception.
In this article, we’ll see how this is done in practice, by looking at some code examples.
2. About Sneaky Throws
Checked exceptions are part of Java, not the JVM. In the bytecode, we can throw any exception from anywhere, without restrictions.
Java 8 brought a new type inference rule that states that a throws T is inferred as RuntimeException whenever allowed. This gives the ability to implement sneaky throws without the helper method.
A problem with sneaky throws is that you probably want to catch the exceptions eventually, but the Java compiler doesn’t allow you to catch sneakily thrown checked exceptions using exception handler for their particular exception type.
3. Sneaky Throws in Action
As we already mentioned, the compiler and the Jave Runtime can see different things:
public static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
throw (E) e;
}
private static void throwSneakyIOException() {
sneakyThrow(new IOException("sneaky"));
}
The compiler sees the signature with the throws T inferred to a RuntimeException type, so it allows the unchecked exception to propagate. The Java Runtime doesn’t see any type in the throws as all throws are the same a simple throw e.
This quick test demonstrates the scenario:
@Test
public void throwSneakyIOException_IOExceptionShouldBeThrown() {
assertThatThrownBy(() -> throwSneakyIOException())
.isInstanceOf(IOException.class)
.hasMessage("sneaky")
.hasStackTraceContaining("SneakyThrowsExamples.throwSneakyIOException");
}
Furthermore, it’s possible to throw a checked exception using bytecode manipulation, or Thread.stop(Throwable), but it’s messy and not recommended.
4. Using Lombok Annotations
The @SneakyThrows annotation from Lombok allows you to throw checked exceptions without using the throws declaration. This comes in handy when you need to raise an exception from a method within very restrictive interfaces like Runnable.
Say we throw an exception from within a Runnable; it will only be passed to the Thread’**s unhandled exception handler.
This code will throw the Exception instance, so there is no need for you to wrap it in a RuntimeException:
@SneakyThrows
public static void throwSneakyIOExceptionUsingLombok() {
throw new IOException("lombok sneaky");
}
A drawback with this code is that you cannot catch a checked exception that is not declared. For instance, if we try to catch the IOException sneakily thrown by the method above, we would get a compilation error.
Now, let’s call throwSneakyIOExceptionUsingLombok and expect Lombok to throw the IOException:
@Test
public void throwSneakyIOExceptionUsingLombok_IOExceptionShouldBeThrown() {
assertThatThrownBy(() -> throwSneakyIOExceptionUsingLombok())
.isInstanceOf(IOException.class)
.hasMessage("lombok sneaky")
.hasStackTraceContaining("SneakyThrowsExamples.throwSneakyIOExceptionUsingLombok");
}
5. Conclusion
As we have seen in this article, we can trick the Java compiler to treat checked exceptions as unchecked.
As always, the code is available over on GitHub.