Java Exception Hierarchy

alex_ber
Geek Culture
Published in
14 min readNov 1, 2020

--

How Exception are used

Introduction

One of the first thing you learn about exception in Java, that there 2 type of them — checked exception and unchecked exception.

Any “exception” is eventually inherits from java.lang.Throwable. It has 2 direct decedents — java.lang.Error and java.lang.Exception. java.lang.RuntimeExceptionis direct decedents of java.lang.Exception.

Unchecked exception are exceptions that inherits from (possible not immediately) java.lang.RuntimeException or java.lang.Error.

Checked exception are exceptions that inherits from (possible not immediately) java.lang.Throwable and not fromjava.lang.RuntimeException and not fromjava.lang.Error. Informally, these are exception that inherits from java.lang.Exception, but not from java.lang.RuntimeException).

Checked exception are not in use in newly designed API, even inside JDK itself.

It is considered biggest design mistake of Java Language.

It is crucial to understand in what layer do you have exception. The treatment will be different.

In the JEE world there are 2 type of exceptions — System Exception and Application Exception. System Exception are usually fatal one and they related to the environment where the program run. For example, DB goes down or OutOfMemoryError occurred. Basically it means, that something goes wrong not in the application code but beyond the scope of application. Application Exception are exceptions that generated by the application. Your application is totally responsible for handling it. I will briefly relate to them below.

In the web-service world (and in micro-service world in particular) we should talk about exceptions that we’re going to return to the caller and exception that occur in one of the internal layers.

As of Java exception that propagates to the caller, in general, it should be transformed to adhere to the protocol that you’re using to communicate with the caller. For example, if you’re writing REST service using Spring Boot, you should define @ExceptionHandler — the classic way or reactive stream way. You should convert your exception into some structured data (typically, JavaBean) that will encode all necessary information from the exception. The Framework will be convert this structured data to appropriate data format (JSON or Protobuf, for example) that will be send to the caller. Also, appropriate HTTP response status should be used if necessary.

All exceptions can be roughly divided into following categorizes: illegal input, some basic business rule is violated, system level problems.

Input Validation

It doesn’t matter, whether another web-service call you or actual user fill the form and you’ve get called first thing that your web-service should do is to validate the input.

If there is multiple parameter the best strategy will be to check all of the and to return exception that will encapsulate all wrong parameters.

Alternatively, we can stop input validation on first wrong parameter that we have found.

Obviously, the former strategy is better, but sometimes you have complex relationship within your input and you don’t want to continue to make input validation if something basically goes wrong.

In JEE world this is application exception.

Should it be checked or unchecked exceptions? For the first layer, it doesn’t really matter, because it will be converted in @ExceptionHandler anyway.

Suppose your web-service call some public method in your other class. Maybe it is Service layer, POJO or some utility class. Obviously, such class should also validate the input. This part of checking of precondition. JDK has built-in function for some simple validation, such as Objects.requireNonNull() or Objects.checkFromToIndex().

For simplicity, let’s consider validation that parameters are not null. What exception should you throw in this case? My point of view that you should “defend” your code by making these kind of check.

Alternative will be not to check and expect your code just to fail in arbitrary point. I’ve seen such approach in couple of projects. The rational is the following: why to bother — I can save time of not typing this code. Eventually this will fail with NullPointerException anyway. The main drawback — your “other class” may call some yet another class and the chain of calls can be deeply nested. You will see that NullPointerException in some 3rd-party library and it will take you time to investigate the root cause. In more complex scenarios, where precondition is more complex, you may even return wrong answer. The best practice is to validate precondition as soon as possible. The drawback — you spend the time for validation that will with high probability never fails (and you “waste your time”).

It is System Exception in JEE world. It is “unexpected exception”. In many cases it point whether to incorrect use of your’s API or to the bug in the code. If it is bug, it clearly has nothing to do with application, you should know about it as soon as possible in order to fix it. If it is incorrect use of API, it means that the problem is beyond responsible of your module, it is “environment” problem, even though the “environment” is just another part of your application. Such exception should be clearly unchecked one, because it is “unexpected exception”.

So, input validation in the first layer is application exception (and can be both checked or unchecked exception, it is converted anyway) and in the other layers is system exception (and unchecked one).

Some basic business rule is violated

So, we have successfully pass precondition check, including input validation and know we’re in some inner layer making some business logic. Suddenly, we’re discovering that some basic business rule is violated. For example, Customer doesn’t exist in the DB. What should we do in this case?

In JEE World this Application exception. In JEE it is part of your contract. Your client should know how to respond in case that some business rule is violated and the main possibility of business rule violation are encoded in the contract of your communication.

In the Spring World this will be unchecked exception (Spring don’t use checked exception anyway), first of all. Typically, it will be generated not in your service layer, but in the DAL layer.

Note:

  1. I’m assuming that DAL layer is not placed in separate micro-service. If you use separate micro-service for the DAL layer, you can just find another example, this is not critical.
  2. If you have business rule violation — this is clearly should be encoded in the contract of your method. But Spring don’t use checked exception for this purpose.

In the ideal word, you should catch such exception in the service layer (or in the first layer) and convert it to something meaningful to your client.

In practice, I’ve seen projects where message that will be shown to the end-user is generated in the place where Application Exception occurred and is put on the Exception object itself or in some “global message container”.

What if we’re calling some other method and we get Application Exception? If this was unchecked exception most likely it will just propagate up to the stack. Of course, you can catch such Application Exception even if it is unchecked. I have used the practice to add such catch-s after I got such exception in production; it is far better to put catch clause in the first place — just read the javadoc (or source-code if javadoc is missing or incomplete).

Ok, you’ve caught Application Exception, what should yo do? Well, because it is Application Exception you should know what to do. For example, maybe you have business rule that says, when you look for some customer in the DB and you didn’t find, you should create it, maybe, with some flag, so you search for customer and got Application Exception indicating that it is not found, so you’re creating new customer with search data (and, maybe, with some flag), save him to the DB and continue your business flow. This may be perfectly fine.

Typically, you will return Application Exception to the caller, maybe wrapped into some other exception.

Another option will be repack this exception to another one. You can catch some exception, analyze it, and than re-throw another exception.
For example, see SQLStateSQLExceptionTranslator and SQLErrorCodeSQLExceptionTranslator.

Note:

  1. These are translators from java.sql.SqlException to DataAccessException (see javadoc of more details).
  2. This translation mechanism is part of Spring Framework. These are Application Exceptions from Spring point of view.

This is not representative example, though. Usually, you will only analyze type and message of the caught exception and will throw another exception with different message and, maybe, type. Typically, you will just wrap this caught exception to another one, passing the caught one as a cause.

System exception

System Exception are usually fatal one and they related to the environment where the program run. For example, DB goes down or OutOfMemoryError occurred. Basically it means, that something goes wrong not in the application code but beyond the scope of application.

Typically they are generated by JVM itself, like OutOfMemoryError or IllegalAccessException (do you know that exists also IllegalAccessError ? :-) ) or some 3rd-part API, like DB driver.

If you have Error you typically has nothing to do, and you just want to crash the JVM (thats why you catch(Throwable) usage should be very limited). So typicall, such Error will propagate it up to the stack to the main() function and beyond.

Besides Error typically system exception will be uncheked one. However, exists some APIs that defines some of the system exception as checked one. In general, this is bad design decision of such APIs. How you should treats them in such a case?

  1. Let propagate it up to the stack to eventually crash the JVM as we do it with Errors. Maybe, you will require to wrap it in some RuntimeException on the way up (or to use some dirty trick to full the compiler — this is not recommended).
  2. Let propagate it to the first level, log it, and return to the caller some generic error message. This is also generally the best strategy with so-called “unexpected exceptions” (see above).
  3. Translate it in more convenient exception for your application (see example above).
  4. Ignore the exception, wait timeout amount of time and retry or make other recovery strategy.
    This can be used if you’re working with some low level API, or you’re calling another web-service and you have some network-related issue that will gone in a short period (some nodes where temporary disconnected or some changes where done and they’re propagating the network; after everything will be up-to-date, it will work — for example, DNS name change or SSL certificate updated).

This means that you work with low level API, if you have to do such things.

Code examples with chained exceptions

Note: All examples works fine in JDK 8. In JDK 9 and later this code works when it runs from the classpath. See my article about Java Platform Module Systemfor more details.

I want to provide you some explicit examples, with demonstration of exceptions chaining (when we wrapped exception to another one, but we’re using the original exception as cause).

Note: Using chained exception is standard practice a long time ago. It was added in JDK 1.4. One of the motivations was java.sql.SQLException and java.rmi.RemoteException and similar class that supports this chaining facility, but not in the same way. So, this mechanism was unified and available to any Throwable.

Example 1.

https://medium.com/@alex_ber/explaining-invokedynamic-number-multiplication-almost-complete-example-part-iv-50b211fd5702

We’re calling some method (it was added in JDK 1.8 aka JDK 8.0) that is documented to throw unchecked ArithmeticException if the result overflows an int. As caller of this method we know that we can get ArithmeticException and if this will happen we will use some alternative strategy to finish the calculation. It is much more slower and takes more memory, in most cases the first attempt will succeed , but it is safe from overflow. See the link above for more details.

Example 2:

https://medium.com/@alex_ber/explaining-invokedynamic-number-multiplication-almost-complete-example-part-iv-50b211fd5702

Here, we using MethodHandles API that throws checked exception in the static initialization block. Checked exception can’t propagate out of static block (actually, it is compilation error), so we have to wrapped them in some unchecked exception. Note, we’re passing the MethodHandles API as cause to improve our ability to understand later what have caused the InternalError to be thrown in the first place.

Example 3:

...
createMethodHandle
is roughly equivalent to:

https://medium.com/swlh/java-assertion-3b3c9611e1dc

I wanted to rewrite original createMethodHandle preserving it’s contract. Exception clause are part of the contract. Specifically, I didn’t want to add new exceptions to exception clause. So, I’ve wrapped this new exceptions to some existing one. It is also make sense, I can say, that if I’ve get NoSuchFieldException into java.lang.reflect.Field of the MethodType, this is IllegalAccessException and if I’ve get SecurityException then it is also IllegalAccessException. This is example of exception translation (there is link to fancier one above).

Note, that I’m insisting to pass SecurityException or NoSuchFieldException as cause to newly generated IllegalAccessException. For historical reasons, it doesn’t have overloaded constructor that takes as one of his parameter the cause, so I’m using initCause() construct that was specifically designed in JDK 1.4 for such a cases. Of course, I could choice just more convenient unchecked exception, but than I’ve changed behavior of the method (it will be “less equivalent”).

More

But this is far from the whole story. What about System exception in the application code itself?

When you’re writing application, when should you throw System exception?

Internal Invariants

Many programmers used comments to indicate their assumptions concerning a program’s behavior. For example:

You can use Java Assertion mechanism in such a case. You can rewrite the previous if-statement like this:

Note, incidentally, that the assertion in the above example may fail if i is negative, as the % operator is not a true modulus operator, but computes the remainder, which may be negative.

https://docs.oracle.com/javase/8/docs/technotes/guides/language/assert.html

This assert statement server for both validation at runtime (when assertions are enabled usually by using -ea flag to java) and for documentation of intent. It is readable and it’s performance impact is minor (Euclidean algorithm is pretty fast and it doesn’t really matter if you’re applying it 2 times or 3 times).

Nevertheless, alternative, is unit-test with good code coverage. You should have unit-test that will cover this else statement.

https://medium.com/swlh/java-assertion-3b3c9611e1dc

This is self-quote from the post about Java Assertion mechanism.

Internal Invariants (switch)

Another good candidate for an assertion is a switch statement with no default case. The absence of a default case typically indicates that a programmer believes that one of the cases will always be executed. The assumption that a particular variable will have one of a small number of values is an invariant that should be checked with an assertion. For example, suppose the following switch statement appears in a program that handles playing cards:

It probably indicates an assumption that the suit variable will have one of only four values…

An acceptable alternative is:

This alternative offers protection even if assertions are disabled, but the extra protection adds no cost: the throw statement won't execute unless the program has failed. Moreover, the alternative is legal under some circumstances where the assert statement is not. If the enclosing method returns a value, each case in the switch statement contains a return statement, and no return statement follows the switch statement, then it would cause a syntax error to add a default case with an assertion. (The method would return without a value if no case matched and assertions were disabled).

https://docs.oracle.com/javase/8/docs/technotes/guides/language/assert.html

Interesting enough, Oracle don’t follow the advice from this documentation, it uses IllegalStateException now on it’s guide examples. See Java Language Update Switch Expressions for example. More on this below.

Quote from Java Language Update Switch Expressions:

Exhaustiveness

Unlike switch statements, the cases of switch expressions must be exhaustive, which means that for all possible values, there must be a matching switch label. Thus, switch expressions normally require a default clause. However, for enum switch expressions that cover all known constants, the compiler inserts an implicit default clause.

https://docs.oracle.com/en/java/javase/14/language/switch-expressions.html

And quote from the JEP 325:

The cases of a switch expression must be exhaustive; for any possible value there must be a matching switch label. In practice this normally means simply that a default clause is required; however, in the case of an enum switch expression that covers all known cases (and eventually, switch expressions over sealed types), a default clause can be inserted by the compiler that indicates that the enum definition has changed between compile-time and runtime. (This is what developers do by hand today, but having the compiler insert it is both less intrusive and likely to have a more descriptive error message than the ones written by hand.)

https://openjdk.java.net/jeps/325

So, Oracle says “don’t bother with default clause” in enum switch expressions, compiler will a) implicitly add it, b) it will throw some exception, c) it will have descriptive error message.

Kotlin, for example, from the beginning don’t have “classic” switch statement. Instead, it use when expression that will be similar to the Java enum switch expression.

It is clear that Java teams try to “fix” switch , in particular, to lift off the “burden” of throwing unchecked exception altogether.

The practical advise will be the following:

  • If you have enum switch expressions, consider to use enhanced switch expression when it will be released.
  • Throw some unchecked exception (it can be AssertionError, but don’t have to be) with default clause.

And some personal note, actually this use-case was the one that motivated me me to use Java Assertion mechanism.

Control-Flow Invariants

Suppose you have a method that looks like this:

You can replace the final comment to throw some unchecked Exception.

It can be AssertionError or it can be, for example, IllegalStateException. It doesn’t really matter, because this exception shouldn’t be executed anyway.

For example:

https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java

Here, as you can see IllegalStateException is thrown in order to please the compiler.

Note: you can’t have unit-test that will cover line 20 above.

Note: In unit-test you shouldn’t cover all possible branches of execution. For example, consider the following code:

Method method = ...
Parameter[] parameters = method.getParameters();
int length = (parameters==null)?0:parameters.length;String[] parameterNames = new String[length];Parameter param = null;for (int i = 0; i < parameters.length; i++) { param = parameters[i]; if (!param.isNamePresent()) { return null; } parameterNames[i] = param.getName();}

Inspired by https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java

Question: Should unit-test be written for the case that parameters is null?

Answer: Of course, no. If you read the java-doc of getParameters() method you will find that it’s contract explicitly state that null can't be returned (array of length 0 can). So, this test is just waste of time.

You can ask another question: why there is a check of nullin the code? If contract says null will be not returned? Well, this is a good question. There is opinion, that such check should be avoided. Especially, when API that is used is JDK itself. It is highly unlikely that we will receive null if API contract states that null will not be returned. Of course, the last statement is true for high-quality API, but JDK is definitely high-quality API. Another reason to remove this check, is otherwise, the mount of validation one should make when it use some API is pretty big and in 99% of times it is just waste of time (both, as development time and as runtime). (Programming style when you’re validating everything and trust nobody is called defensive programming; it is good sometimes, but in general is not advised).

Well, I agree with point provided above at most. But I see no harm to make some simple check that can avoid application crash. I have 2 conter-arguments: bug and ease of reasoning.

Bug argument. What if regardless of what API contract says, the method will return some day null? It can even happen with JDK, in some JDK update bug can be introduced. Again, if the check is simple why not to take proactive action and put some defense? Shit happens…

Ease of reasoning. Most of the time you’re calling some API with not clear quality. More over, you don’t want to spend your time to figure out whether is well-established open source project or one man maintainers project. You can argue, that you should use only well established good quality code base, but sometimes you just don’t have some option. For example, you have some niche requirement that somebody wrote an answer as open source, but it is not well established project. Or you got this API as transitive dependencies of well-established project and it contains functionality that is useful also directly for you. So, when you read the code you don’t want to think, “well, in this case I’m using JDK, so I can omit null check and in this case it is some unknown API, so null check should be put it place”. If the validation is simple, it is far more simple to put it in every place and when you read the code (and we read the code more often that we write the code) just don’t bother. Otherwise, may be it is bug, may be you code is calling some untrusted code and the null check was just forgot?

You can find a few more cases in Java Assertion mechanism, but personally I never used them in practice.

--

--