How to throw an exception in Java
A step-by-step guide with code examples on how to use Java's error-handling technique, differentiate between checked and unchecked exceptions, and more.
Dec 06, 2023 • 12 Minute Read
“Yes! My application finally works!” I hear you say. I know you’ve said it at some point --- accompanied by a huge rush of endorphins, validation, and relief --- because I’ve said it myself hundreds of times over my 25 years as a Java coder. In fact, if it’s a particularly tricky problem, it’s not uncommon for me to hoot, cheer, and even stand and do a few finger snaps to celebrate (I work remotely).
But many times when we say “it works!”, we are really saying that we’ve got the “happy path” done. We’ve accounted for how things will work only if our end user makes no mistakes and our application has infinite resources and zero bugs. We ultimately know this isn’t true, but it’s nice to live in denial momentarily.
The truth is that often more than half the battle is error-handling. In Java’s case, that means exceptions.
The happy path vs the right one
Let’s consider the following Java application that asks an end user for two numbers and divides them:
java
public class Divider {
public static void main(String… args) {
Console console = System.console();
Double numerator = Double.valueOf(console.readLine(“Enter the numerator: “));
Double denominator = Double.valueOf(console.readLine(“Enter the denominator: “));
Double quotient = numerator / denominator;
System.out.printf(“The quotient is %f%n”, quotient);
}
}
The happy path works just fine:
bash
Enter the numerator: 3.2
Enter the denominator: 1.6
The quotient is: 2.000000
Can you think of anything that could go wrong with this application? Pause and see how many things you can come up with.
Did you find anything? Here are a few:
The user could enter something that’s not a number, resulting in a `NumberFormatException`
The user could enter a zero for the denominator, resulting in an `ArithmeticException`, and
`System#console` could return `null` if there is no console available, resulting in a `NullPointerException`
In this article, we’ll examine the first two of these.
The basics of error handling in Java
If you take a look at the JavaDoc for `Double#valueOf`, you’ll see the following explanation:
Throws:
NumberFormatException - if the string cannot be parsed as an integer.
With that information in mind, you should be able to recognize what’s happening in this run of `Divider`:
bash
Enter the numerator: the numerator
Exception in thread "main" java.lang.NumberFormatException: For input string: "the numerator"
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2054)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:651)
at java.base/java.lang.Double.valueOf(Double.java:614)
at com.pluralsight.joshcummings.Divider.main(Divider.java:8)
When the user entered an invalid input, `Double#valueOf` threw an exception.
And, as a result, the program didn’t proceed any further. This is because exceptions interrupt the natural flow of an application. The program cannot proceed further since it is now in an invalid state.
You can also see that while exceptions protect an application from being in an invalid state, not handling them means an ugly and confusing experience for your user.
What you are seeing right now is the exception and its stack trace, or the exact line where the exception was thrown followed by the series of method calls that led to that line. This is helpful for debugging purposes, but not likely helpful to your end user.
How to handle exceptions in Java
We can improve this situation by handling the exception. Consider an improved version of `readLine` that keeps asking the user until they provide valid input:
java
private static Double readDouble(Console console, String message) {
while (true) {
Double value = Double.valueOf(console.readLine(message));
if (value is valid) {
return value;
} else {
System.err.println(“Sorry, that input was invalid, please try again”);
}
}
}
The above pseudocode handles the error by checking and then looping to ask again. In Java, the way to do this is by catching the exception, like so:
java
private static Double readDouble(Console console, String message) {
while (true) {
try {
return Double.valueOf(console.readLine(message));
} catch (NumberFormatException ex) {
System.err.println(“Sorry, that input was invalid, please try again”);
}
}
}
Then, we can change the original `main` method to look like this:
java
public class Divider {
public static void main(String… args) {
Console console = System.console();
Double numerator = readDouble(console, “Enter the numerator: “);
Double denominator = readDouble(console, “Enter the denominator: “);
Double quotient = numerator / denominator;
System.out.printf(“The quotient is %f%n”, quotient);
}
}
And the ensuing behavior is much more user-friendly and resilient:
bash
Enter the numerator: the numerator
Sorry, that input was invalid, please try again.
Enter the numerator: 3.2
Enter the denominator: …
How to communicate user error
We can use this same principle to our advantage when we want to interrupt the flow of the application ourselves.
For example, let’s now add a dedicated `divide` method to address the divide-by-zero issue. It might look something like this:
java
private static Double divide(Double numerator, Double denominator) {
if (denominator == 0) {
return null;
}
return numerator / denominator;
}
This is an example of handling an error by returning a sentinel value. While this does prevent the application from entering into an invalid state, it has the potential drawback of not interrupting application flow. Such an application would behave like this:
java
Enter the numerator: 12
Enter the denominator: 0
The quotient is null
In our case, we’d prefer to not proceed with the rest of the application since the input is invalid. So, instead of returning a sentinel value, let’s throw Java’s `ArithmeticException`, providing a helpful message in the process:
java
private static Double divide(Double numerator, Double denominator) {
if (denominator == 0) {
throw new ArithmeticException(“Sorry, you cannot divide by zero.”);
}
return numerator / denominator;
}
And then, you guessed it, we need to handle it so that the end user sees that helpful message instead of a confusing stack trace like so:
java
public class Divider {
public static void main(String… args) {
Console console = System.console();
Double numerator = readDouble(console, “Enter the numerator: “);
Double denominator = readDouble(console, “Enter the denominator: “);
try {
Double quotient = divide(numerator, denominator);
System.out.printf(“The quotient is %f%n”, quotient);
} catch (ArithmeticException ex) {
System.err.println(ex.getMessage());
}
}
}
Next, let’s enhance the `divide` method in the following two ways:
Make the error case self-documenting
Add a custom exception for additional clarity
How to document exceptions
As you know, a method signature states what inputs are needed and what output can be expected when you invoke the method. It can also describe what exceptions it might throw.
If you change the `divide` method signature to this:
java
private Double divide(Double numerator, Double denominator) throws ArithmeticException
This helps clarify to coders and IDEs alike what the potential hazards of your method are without having to inspect the method body (which they may not have access to anyway).
Now, there are a lot more exceptions that a given method might throw that have nothing precisely to do with your business logic. A famous one is `NullPointerException`, which is an exception that Java throws whenever you try and operate on a null reference. Others like this are `IllegalArgumentException` and `IllegalStateException`. Stating these in the method signature doesn’t provide more information about the kinds of errors that might occur. Because of that, it’s not helpful to add them to the method signature.
But, whenever the exception is an important expression of business logic, you should place it in your method signature for clarity.
How to throw custom exceptions
Let’s illustrate this principle further by creating a custom exception. An exception is just a Java class, which means that it can be extended. Usually, exceptions are extended for two reasons:
Different types can be placed in different catch blocks
The extension’s class name is clearer about what went wrong
To see the second one in action, consider the following custom exception that extends `ArithmeticException`:
java
public class DivideByZeroException extends ArithmeticException {
public DivideByZeroException() {
super(“Sorry, you cannot divide by zero.”);
}
}
Having done this, we can change the divide method like so:
java
private static Double divide(Double numerator, Double denominator) throws DivideByZeroException {
if (denominator == 0) {
throw new DivideByZeroException();
}
return numerator / denominator;
}
You’ll notice two changes. First, we throw our custom exception. Second, our method signature is more descriptive as to what could go wrong.
Now you know enough to do something a bit more advanced! Let’s make another change to our application to illustrate how throwing an exception affects the programming flow and some related best practices around checked and unchecked exceptions.
How to propagate exceptions
Let’s imagine that we introduce a method called `run`, which is going to run our program. This could be helpful in case we want to control when it gets run and how many times it gets run. Here’s what our new `main` and `run` methods might look like:
java
public static void main(String… args) {
try {
run();
} catch (Exception ex) { // a trick to catch all exceptions!
System.err.printf(“An error occurred: %s%n“, ex.getMessage());
}
}
private static void run() {
Console console = System.console();
Double numerator = readDouble(console, “Enter the numerator: “);
Double denominator = readDouble(console, “Enter the denominator: “);
Double quotient = divide(numerator, denominator);
System.out.printf(“The quotient is %f%n”, quotient);
}
// …
Notice that there is no more `catch` block around the call to `divide`. This works since Java automatically propagates an exception up to the first `catch` block that it finds that handles that exception or one of that exception’s superclasses.
In other words, when our `DivideByZeroException` is thrown, Java passes it up to the `run` method. Seeing that `run` doesn’t handle it, it goes up to the next method in the stack trace, `main`. Seeing a `catch` block there that handles `Exception`, a superclass of `DivideByZeroException`, Java moves the program flow there.
You can see this by analyzing that the stacktrace changed from this:
Exception in thread "main" com.pluralsight.joshcummings.DivideByZeroException: Sorry, you cannot divide by zero
at sample.config.Divider.divide(Divider.java:26)
at sample.config.Divider.main(Divider.java:10)
to this:
Exception in thread "main" com.pluralsight.joshcummings.DivideByZeroException: Sorry, you cannot divide by zero
at sample.config.Divider.divide(Divider.java:34)
at sample.config.Divider.run(Divider.java:18)
at sample.config.Divider.main(Divider.java:8)
There are times when we might want to provide more context, though, as an exception moves up the call stack. For example, `run` might simply want to indicate whether it was a user error or an application error.
We can propagate a lower-level exception into a higher one by including the lower in the higher’s constructor, like so:
java
private static void run() {
Console console = System.console();
Double numerator = readDouble(console, “Enter the numerator: “);
Double denominator = readDouble(console, “Enter the denominator: “);
try {
Double quotient = divide(numerator, denominator);
System.out.printf(“The quotient is %f%n”, quotient);
} catch (DivideByZeroException lowerLevelException) {
throw new IllegalArgumentException(lowerLevelException);
}
}
Including the causing exception in the constructor ensures that all the debugging information is preserved when you are trying to figure out later on what caused this or that error to occur, which you can again see by the new stack trace that includes both exceptions:
Exception in thread "main" java.lang.IllegalArgumentException: Sorry, you cannot divide by zero
at sample.config.Divider.run(Divider.java:22)
at sample.config.Divider.main(Divider.java:8)
Caused by: com.pluralsight.joshcummings.DivideByZeroException: Sorry, you cannot divide by zero
at sample.config.Divider.divide(Divider.java:38)
at sample.config.Divider.run(Divider.java:19)
... 1 more
Why you should favor throwing runtime exceptions
Now, if you’ve read anything about Java exceptions, you’ve certainly heard someone mention that Java has both checked and unchecked exceptions.
The examples we’ve been working with so far are unchecked exceptions. It means that your code will still compile even if an exception is not handled (caught in a `catch` block or listed in a `throws` block). On the other hand, checked exceptions are ones where the Java compiler checks that you handled it, or compilation fails.
We’ll see an example in a moment. First, though, in my humble (and always correct) opinion, unchecked exceptions are the ideal. In my 25 years as a Java coder, I have yet to run into a situation where a checked exception would have been preferred. Whenever I advise a junior coder who has used a checked exception, I explain that no other programming language before or since had checked exceptions, it is a failed experiment, and I invite them to use an unchecked exception instead lest God kill another kitten.
Hopefully my position is clear.
When code throws a checked exception, it must be handled immediately or compilation fails. The implications of this policy are a little clearer in larger applications with lots of nesting. For now, though, let’s just learn the difference.
To illustrate, let’s go back to an earlier iteration of our `run` method:
java
private static void run() {
Console console = System.console();
Double numerator = readDouble(console, “Enter the numerator: “);
Double denominator = readDouble(console, “Enter the denominator: “);
Double quotient = divide(numerator, denominator);
System.out.printf(“The quotient is %f%n”, quotient);
}
Any exception that extends from `RuntimeException` is an unchecked exception (or an exception whose handling does not need to be dealt with until runtime). If you look at the object hierarchy for `ArithmeticException`, you’ll see that it’s an unchecked exception:
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.lang.RuntimeException
java.lang.ArithmeticException
If you instead extend from `Exception`, then it is a checked exception. This means that you are telling the compiler to fail if the calling code doesn’t handle your exception.
For example, let’s say that our `DivideByZeroException` extended from `Exception` instead of `ArithmeticException`:
java
public class DivideByZeroException extends Exception
Now compilation will say:
bash
com/pluralsight/joshcummings/Divider.java:18: error: unreported exception DivideByZeroException; must be caught or declared to be thrown
Double quotient = divide(numerator, denominator);
To fix this, we need to do one of three less-than-ideal things. `run` must either change to this:
java
private static void run() throws DivideByZeroException {
Console console = System.console();
Double numerator = readDouble(console, “Enter the numerator: “);
Double denominator = readDouble(console, “Enter the denominator: “);
Double quotient = divide(numerator, denominator);
System.out.printf(“The quotient is %f%n”, quotient);
}
... which is not ideal, since `run` certainly has nothing to do with “arithmetic”; to this:
java
private static void run() {
Console console = System.console();
Double numerator = readDouble(console, “Enter the numerator: “);
Double denominator = readDouble(console, “Enter the denominator: “);
try {
Double quotient = divide(numerator, denominator);
System.out.printf(“The quotient is %f%n”, quotient);
} catch (DivideByZeroException ex) {
System.err.printf(“An error occurred: %s%n“, ex.getMessage());
}
}
....which is not ideal, since it duplicates error-handling logic in `main`; or to this:
java
private static void run() {
Console console = System.console();
Double numerator = readDouble(console, “Enter the numerator: “);
Double denominator = readDouble(console, “Enter the denominator: “);
try {
Double quotient = divide(numerator, denominator);
System.out.printf(“The quotient is %f%n”, quotient);
} catch (DivideByZeroException ex) {
throw new IllegalArgumentException(ex, ex.getMessage());
}
}
...which is not ideal, since the extra boilerplate is forced on the coder instead of allowing the coder to consider overall readability and API clarity.
In general, checked exceptions aren’t preferred since they tend to clutter up method signatures and calling code that may not have the appropriate context to be able to handle the exception.
In all fairness, some have argued that checked exceptions force good API design in the same way that Python forces clean code with mandatory indentation. It’s conceivable that with a more concise exception-handling syntax this may be true. However, we need only refer ourselves to tools like Lombok’s `@SneakyThrows` to understand the Java community's general rejection of this idea.
Conclusion
In this article, you’ve learned about Java’s exception-handling capabilities, specifically around throwing exceptions. The JDK itself commonly throws exceptions to indicate invalid usage or an invalid state, and you can do the same with the code that you write. When an exception is thrown, the program flow moves to the closest applicable catch block in the call stack.
If you are wrapping and re-throwing an exception you’ve caught, remember to include the original exception in the constructor so the low-level cause isn’t lost. Finally, favor throwing unchecked exceptions as it typically makes for more readable code.
Learning more about Java
If you’re keen to upgrade your Java skills, Pluralsight also offers a wide range of Java and Spring related courses that cater to your skill level. You can sign up for a 10-day free trial with no commitments. You can also perform an equally free roleIQ assessment to see where your Java skills currently stack up, with advice on where to shore up the gaps in your current skill set.
Below is a list of Pluralsight learning paths with courses for beginner, intermediate, and advanced Java courses — just pick the starting point that works for you. If you’re not sure where you’re at, each page has a skill assessment you can use that will recommend where you should start out.
And if you liked this article, here are some courses written by me that you can check out.