There has recently been some debates on /r/java at Reddit [1] [2] about the current state of exceptions in Java. Some see them as essential, others think they just get in their way, while yet others think they are valuable only in the right contexts. If you have experience with more than one or two programming languages, you might now that exception handling is a notoriously hard problem. Whatever solution is selected by the language designers typically leaves at least some people disappointed. Why is that? Why is it so hard to get right? Why not take this opportunity to try to figure out why! While we are at it, we might even try to figure out a better kind of exception handling for Java!.
Why Exceptions?
The point of having an exception handling system to be able to separate the intended flow of execution from the one dealing with exceptional scenarios. It sounds great in theory, and often is in practice. Either you can send the message or it fails. Two scenarios, two flows, one golden path and one for recovering. You don’t have to check the return value after every method invocation, and you can just delegate the handling of an exception to the caller of your method, if that would be the most appropriate.
An exception is a form of communication—just like a regular return value—and it is up to the exception handling system to help the caller and the callee to work together. The outcome must be correct and performant enough. There are some caveats to this communication, however.
The Caveats of Exception Handling
What Is a Failure Is up to Interpretation
Let’s consider two scenarios where a file is read from disk. In the first scenario, the file must be present and contain certain configuration parameters, or the computer program cannot render any value at all. In the second scenario, the file is read and reacted to if it’s present, but the application can continue as normal without it.
String readSomeFile() {
try {
return Files.readString(Path.of("/some/path"));
} catch (NoSuchFileException e) {
// Is this a failure?
}
}
Now, is the inability to read a missing file from disk a failure? In the first scenario, it clearly is. The file must be present or the application is useless. But in the second scenario, it is not. The application sees the file being present and being missing as two completely acceptable situations.
This highlights one of the big problems of designing a good exception handling system: what is a failure is up to interpretation. This becomes particularly relevant when there is a cost to throwing exceptions, such as the cost of a stack trace, or when the caller must add ceremony to handle the exception, as when having to catch or redeclare checked exceptions.
Only Unexpected Failures Warrant Investigation
Broadly speaking, a method can produce two kinds of results:
- those we expect to happen, and
- those we don’t.
void handleEvent(String name) {
switch (name) {
case "a" -> handleEventA();
case "b" -> handleEventB();
case "c" -> handleEventC();
default ->
// This should never happen, but we know it might!
throw new UnknownEventName(name);
}
}
If we know that files are likely to be missing or to contain invalid data, then we typically add all the code necessary to handle these scenarios. But when there are things that can go wrong we don’t anticipate, the best we can to is to collect all the data we can about the situation and make sure it reaches us at some point. Only unexpected failures warrant investigation, and investigation requires data. An exception handling system can help us collect this—via stack traces or otherwise. Ideally, we can reconstruct the entire chain of application states that lead to the unexpected outcome.
Not All Theoretically Possible Failures Apply in Practice
Not every possible outcome of an application routine is equally likely. We might, for example, be completely aware of the fact that our application can run out of memory and still not want to write any code for handling the situation. Perhaps we know that the hardware will power down and reboot in this case, which we see as an acceptable outcome.
You, as a developer, will often know things about your system that the designers of the programming language you are using do not and can not know.
void aimAndLaunchSpaceship() {
// We don't care if it crashes.
performComplexCalculationConsumingEnourmousAmountsOfMemory();
launchSpaceship();
}
The Future of Exceptions in Java
Now that we have been primed on some of the intricacies of exceptions, let’s consider how we could improve them in Java!
Globally Configurable Checking
The discussion about Java exceptions on Reddit is, at least in part, about who gets to decide what exceptions must be explicitly handled. If an exception is checked, then every caller must either catch it or redeclare it, both of which is a form of ceremony. If you make an exception unchecked, it is up to the caller to decide how much ceremony to introduce.
In this case, the tension comes from the fact that what is a failure is up to interpretation. For an exception to be justifiably checked, it must be interpreted as a failure in just about every possible scenario. There are few, if any, exceptions that fit this bill. I agree with Stuart Marks [1] in the sense that if there are any such exceptions, they should indeed be checked.
A way to solve this tension may be to allow the programmer to globally declare what exceptions must be checked. This could also be a way to preserve backwards-compatibility, as not making a decision would simply preserve the legacy behavior. It also means that different Java subcommunities can work out their own preferences on default behavior.
Listing All Possible Exceptions
This is not really a compiler feature, but one for a language server. I have many times wished for a list of all exceptions that could possibly be thrown by a method. You might think that this is something that is solved for checked exceptions, but you are allowed to specify a superclass of the thrown exceptions in the throws clause, hiding the exceptions actually being thrown.
In my mind, the ideal place for this list would be in the documentation that appears when I hover a method in my IDE. I would also be happy to see this information in generated Javadocs.
Catch-Site Capture Declarations
By default, a created exception lazily captures its call stack and includes it as a stack trace. This is a great feature, but stack traces often contain too much and too little information at the same time. Frameworks often bloat the call stacks with their own code, and key frames are often missing when call chains are asynchronous or rely heavily on function composition.
A possible solution could be the following:
String method() {
try {
return Files.readString(path);
} catch (NoSuchFileException e without stacktrace) {
// `e` has no stack trace. Not even a lazily intitialized one.
} catch (IOException e with stacktrace, variables) {
// `e` has strack trace and local variable captures with each frame.
}
}
The syntax would likely need some polishing, but the idea is simple: Allow for the consumer of the exception to declare what information to collect when the exception is created. The point is to support investigation into unexpected failures, not to help with exception recovery. The latter would still have to be supported via custom exception classes with relevant property members.
Pattern Matching On Exceptions
This is an idea that has been floating around for a while, but I want to repeat it because I think its such a great idea! The point is to allow exceptions to be caught in switch statements, as in:
void method(Path path) {
switch (Files.readString(path)) {
case NoSuchFileException _ -> {}, // Handle error.
case IOException _ -> {}, // Handle error.
case String file -> {}, // Handle success.
}
}
The beauty of it is that it seems highly familiar to anyone who already underastands pattern matching in Java, while removing the cermony of the try/catch blocks.
Conclusion
Java is moving ahead, and I’m hopeful that also exception handling is going to get more accurate, performant and convenient in the years ahead. What do you think of these ideas? Do you have any of your own?

Leave a comment