A wicked Java trick to make the JVM forget to check exceptions
I’ve long time been a critic of the mechanism of compiler checked exceptions in Java. Whether you love them or hate then, one thing is sure: There’s situations where you don’t want to have to deal with them. The solution in Java is to wrap a checked exception in new RuntimeException(e) but this gives long stack traces without adding useful information. Sometimes, we just want to tell the compiler to chill.
As it turns out this is possible, through some wicked abuse of the type erasure misfeature of Java-generics. Seeing this in action is instructive for understanding the inner workings for Java. Let’s go!
Here is what we want:
public static void main(String[] args) {
businessLogic();
}
private static void businessLogic() {
List configuration = readConfigurationFile();
System.out.println(configuration.get(0));
}
private static List readConfigurationFile() {
try {
return Files.readAllLines(Paths.get("non", "existing", "file"));
} catch (IOException e) {
throw softenException(e);
}
}
Notice that the businessLogic() neither catches IOException or declares that it throws IOException. Instead, the softenException() method removes the checkedness of the exception. When we run it, we get the following stack trace:
Exception in thread "main" java.nio.file.NoSuchFileException: non\\existing\\file
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:230)
at java.nio.file.Files.newByteChannel(Files.java:361)
at java.nio.file.Files.newByteChannel(Files.java:407)
at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:384)
at java.nio.file.Files.newInputStream(Files.java:152)
at java.nio.file.Files.newBufferedReader(Files.java:2784)
at java.nio.file.Files.readAllLines(Files.java:3202)
at java.nio.file.Files.readAllLines(Files.java:3242)
at insanejava.SoftenExceptionsDemo.readConfigurationFile(SoftenExceptionsDemo.java:21)
at insanejava.SoftenExceptionsDemo.businessLogic(SoftenExceptionsDemo.java:15)
at insanejava.SoftenExceptionsDemo.main(SoftenExceptionsDemo.java:11)
Huh! The exception being thrown in the main method is a NoSuchFileException, which is a subclass of IOException - a checked exception! How can that be? Why didn’t any of the methods in the program have to declare throws IOException?
Here’s the trick:
private static RuntimeException softenException(Exception e) {
return checkednessRemover(e);
}
private static T checkednessRemover(Exception e) throws T {
throw (T) e;
}
The checkednessRemover method uses a trick that reveals a few things about the inner workings of Java. First, the generic type argument T is bound to RuntimeException in order to fulfill the contract of softenException. This means that the expression throws T becomes throws RuntimeException, which the compiler interprets as if there was no exceptions thrown.
But the statement throw (T)e; theoretically should evaluate to throw (RuntimeException)e;. Since e is a NoSuchFileException, you would expect this statement to result in a ClassCastException. But the way generics work in Java, the type information is removed by the compiler. So instead, the bytecode reads as throw (Exception)e;, which is fine.
So this strange trick shows that the Java compiler removes generic information from the compiled code and that checked exceptions is purely a feature of the compiler. There are no runtime verifications of checked exceptions.
Would I recommend using this trick in production code? I don’t know. It’s pretty weird and may not be that useful, but I do use it myself when I’m feeling wicked. If nothing else, I hope learning about has given you some insights into the inner workings of Java.
Disclaimers: (1) I read about this trick somewhere else, but I cannot find the source any longer. I thought it was Heinz Kabutz’ excellent Java Specialist newsletter, but I can’t find the source. (2) This is also implemented in Project Lombok as @SneakyThrows. If you are using Lombok, you should under no circumstance reimplement the trick from this blog. Use @SneakyThrows instead.
Comments:
[logos01] - May 21, 2018
Nice trick, I always simply use new RuntimeException(thecheckedexception), which gives more insight on the stacktrace.
[Vicente Rossello] - May 21, 2018
Of course I use it in production, it’s much better and concise than throw new RuntimeException(e); or throw new IllegalStateException(e); Shorter stacktrace!
[Peter Verhas] - May 22, 2018
it can be handy with streaming API. The longer stack trace embedding the exception into a RuntimeException may or may not deliver vluable information and the longer stack trace may or may not be a nuisance.