Java Exception Handling Best Practices
Introduction
Knowing the syntax of
(Note - This tutorial builds on the earlier exception tutorials, from Checked vs Unchecked ExceptionsClick to read tutorial: Checked vs Unchecked Exceptions in Java to try-with-resourcesClick to read tutorial: Java try-with-resources and AutoCloseable Explained.) 1. Never swallow exceptions An empty
OUTPUT of the above code
Every
OUTPUT of the above code
The broad catch quietly covered up a bug. The specific catch let the
3. Keep the cause when wrapping
When you translate one exception into another, pass the original as the cause. Without it, the stack trace and message of the real problem are gone.
OUTPUT of the above code
4. Log or rethrow - not both
Logging an exception and then rethrowing it means it gets logged again by every layer that does the same - one problem shows up five times in the logs, each with a full stack trace. Decide in one place: either handle (and log) the exception, or pass it on. Usually, the code which finally handles it is the one which logs it.
5. Do not use exceptions for normal control flow
Exceptions are for exceptional situations. Using them for ordinary decisions - such as ending a loop - makes code hard to read, hides real bugs among the expected exceptions, and is slow, because creating an exception captures the whole stack trace.
OUTPUT of the above code (timings vary from run to run)
The same applies to input: check with
6. Fail fast, with a useful message
Check arguments at the start of a method and throwClick to read tutorial: throw vs throws in Java with Examples immediately when they are invalid, instead of letting bad values travel deep into the program and fail somewhere unrelated. Make the message say what was wrong, and include the value.
OUTPUT of the above code
7. Handle exceptions at the right level
Catch an exception where you can actually do something useful about it - retry, use a default, or tell the user. Low-level code, such as a method which reads a file, usually cannot know what the right response is, so it should let the exception propagate (or wrap it in a meaningful custom exceptionClick to read tutorial: Creating Custom Exceptions in Java with Examples). The top of the application - the user interface or the request handler - decides what the user sees. Catching everything immediately, everywhere, leads to many half-handled exceptions and duplicated error code.
8. Clean up with try-with-resources, and never return from finally
Use try-with-resourcesClick to read tutorial: Java try-with-resources and AutoCloseable Explained for every resource, rather than closing it by hand in
try, catch and throw is the easy part. Handling exceptions well - so that problems are reported clearly, nothing is silently lost, and the code stays readable - takes judgment. This tutorial collects the most important Java exception handling best practices, each with a bad example and a better one. We will start with the mistakes that hide problems: empty catch blocks and catching too broadly. We will then look at losing the cause, logging and rethrowing, and using exceptions for normal control flow. Next we will see how to fail fast with good messages, and where exceptions should be handled. Lastly, we will round off with a checklist.(Note - This tutorial builds on the earlier exception tutorials, from Checked vs Unchecked ExceptionsClick to read tutorial: Checked vs Unchecked Exceptions in Java to try-with-resourcesClick to read tutorial: Java try-with-resources and AutoCloseable Explained.) 1. Never swallow exceptions An empty
catch block - swallowing an exception - is the worst exception handling mistake. The problem still happened, but now nobody will ever know, and the program carries on with bad data.
A swallowed exception hides the real problem
//SwallowedException.java
public class SwallowedException {
static int parseDiscount(String text) {
int discount = 0;
try {
discount = Integer.parseInt(text);
} catch (NumberFormatException e) {
// ignore
}
return discount;
}
public static void main(String[] args) {
int discount = parseDiscount("15%");
System.out.println("Discount applied: " + discount + "% - and nobody knows why it is not 15%");
}
}
Discount applied: 0% - and nobody knows why it is not 15%
catch block should do at least one of these: recover (with a sensible, intended default), report (log it), or rethrow (let someone else handle it). In the rare case where ignoring really is correct, write a comment saying why, and name the variable ignored.
2. Catch specific exceptions, not Exception or Throwable
catch (Exception e) catches everything - including bugs such as NullPointerException that you never meant to handle - and treats them all as if they were the one problem you expected. catch (Throwable t) is worse, as it also catches Errors such as OutOfMemoryError.
Catching too broadly treats a bug as an expected problem
//TooBroad.java
public class TooBroad {
static String[] config = null; // a bug: config was never loaded
static int portBroad() {
try {
return Integer.parseInt(config[0]);
} catch (Exception e) { // too broad
System.out.println(" broad catch: 'invalid port, using 8080' - but the real problem was " + e.getClass().getSimpleName());
return 8080;
}
}
static int portSpecific() {
try {
return Integer.parseInt(config[0]);
} catch (NumberFormatException e) { // only the problem we can handle
System.out.println(" invalid port, using 8080");
return 8080;
}
}
public static void main(String[] args) {
System.out.println("Port: " + portBroad());
System.out.println("Port: " + portSpecific());
}
}
broad catch: 'invalid port, using 8080' - but the real problem was NullPointerException Port: 8080 Exception in thread "main" java.lang.NullPointerException: Cannot load from object array because "TooBroad.config" is null at TooBroad.portSpecific(TooBroad.java:16) at TooBroad.main(TooBroad.java:25)
NullPointerException through, and its message points straight at the cause. Catch Exception only at the very top of an application - for example, around each request in a server - to log the failure and keep running.
Wrapping with and without the cause
//KeepTheCause.java
public class KeepTheCause {
static int loseCause(String text) {
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Bad quantity"); // cause lost
}
}
static int keepCause(String text) {
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Bad quantity: " + text, e); // cause kept
}
}
public static void main(String[] args) {
try {
loseCause("3x");
} catch (IllegalArgumentException e) {
System.out.println("Without cause: " + e.getMessage() + ", cause = " + e.getCause());
}
try {
keepCause("3x");
} catch (IllegalArgumentException e) {
System.out.println("With cause : " + e.getMessage() + ", cause = " + e.getCause());
}
}
}
Without cause: Bad quantity, cause = null With cause : Bad quantity: 3x, cause = java.lang.NumberFormatException: For input string: "3x"
An exception as a loop exit vs a normal check
//ControlFlow.java
public class ControlFlow {
public static void main(String[] args) {
int[] values = new int[10];
for (int i = 0; i < values.length; i++) values[i] = i;
long start = System.nanoTime();
long sum1 = 0;
for (int round = 0; round < 200_000; round++) {
try {
int i = 0;
while (true) sum1 += values[i++]; // ends with an exception
} catch (ArrayIndexOutOfBoundsException e) {
// loop finished
}
}
long withExceptions = (System.nanoTime() - start) / 1_000;
start = System.nanoTime();
long sum2 = 0;
for (int round = 0; round < 200_000; round++) {
for (int i = 0; i < values.length; i++) sum2 += values[i]; // ends with a check
}
long withChecks = (System.nanoTime() - start) / 1_000;
System.out.println("Same sums: " + (sum1 == sum2));
System.out.println("Loop ended by exceptions : " + withExceptions + " microseconds");
System.out.println("Loop ended by a condition: " + withChecks + " microseconds");
}
}
Same sums: true Loop ended by exceptions : 31512 microseconds Loop ended by a condition: 10161 microseconds
hasNextInt() or validate a string before parsing it when bad input is expected, as shown in the Scanner tutorialClick to read tutorial: Reading User Input in Java with Scanner, rather than relying on catching NumberFormatException every time.
Good and bad exception messages
//FailFast.java
import java.util.Objects;
public class FailFast {
static void setDiscountBad(int percent) {
if (percent < 0 || percent > 100) throw new IllegalArgumentException("Invalid");
}
static void setDiscountGood(int percent) {
if (percent < 0 || percent > 100) {
throw new IllegalArgumentException("Discount must be between 0 and 100, but was " + percent);
}
}
static String greet(String name) {
Objects.requireNonNull(name, "name must not be null");
return "Hello, " + name;
}
public static void main(String[] args) {
try { setDiscountBad(150); } catch (IllegalArgumentException e) { System.out.println("Bad message : " + e.getMessage()); }
try { setDiscountGood(150); } catch (IllegalArgumentException e) { System.out.println("Good message: " + e.getMessage()); }
try { greet(null); } catch (NullPointerException e) { System.out.println("Null check : " + e.getMessage()); }
}
}
Bad message : Invalid Good message: Discount must be between 0 and 100, but was 150 Null check : name must not be null
finally. Never put a return or throw in a finally block - as shown in the finally tutorialClick to read tutorial: Java try, catch, finally and Multi-catch with Examples, it silently overrides results and swallows exceptions.
Checklist
- No empty
catchblocks - every catch recovers, reports or rethrows. - Catch the most specific exception types; catch
Exceptiononly at the top level. - Always pass the cause when wrapping an exception.
- Log an exception once, where it is handled - not at every layer.
- Use checks, not exceptions, for expected situations and loop control.
- Validate arguments early, and write messages which include the bad value.
- Choose checked or uncheckedClick to read tutorial: Checked vs Unchecked Exceptions in Java deliberately, and prefer standard exceptions when they fit.
- Handle exceptions where something useful can be done about them.
- Use try-with-resources for resources, and never return from
finally. - Document the exceptions a method throws, with
throwsand the Javadoc@throwstag.
finally trap, and summarized everything in a checklist. This completes the exception handling module. Next, we start on generics with an introduction to Java genericsClick to read tutorial: Introduction to Java Generics: Why and How.Core Java Tutorials
Java try-with-resources and AutoCloseable ExplainedClick to read tutorial: Java try-with-resources and AutoCloseable Explained Introduction to Java Generics: Why and HowClick to read tutorial: Introduction to Java Generics: Why and How Checked vs Unchecked Exceptions in JavaClick to read tutorial: Checked vs Unchecked Exceptions in Java Java try, catch, finally and Multi-catch with ExamplesClick to read tutorial: Java try, catch, finally and Multi-catch with Examples throw vs throws in Java with ExamplesClick to read tutorial: throw vs throws in Java with Examples Creating Custom Exceptions in Java with ExamplesClick to read tutorial: Creating Custom Exceptions in Java with Examples Java Math Class: Essential Methods with ExamplesClick to read tutorial: Java Math Class: Essential Methods with Examples
Java try-with-resources and AutoCloseable ExplainedClick to read tutorial: Java try-with-resources and AutoCloseable Explained Introduction to Java Generics: Why and HowClick to read tutorial: Introduction to Java Generics: Why and How Checked vs Unchecked Exceptions in JavaClick to read tutorial: Checked vs Unchecked Exceptions in Java Java try, catch, finally and Multi-catch with ExamplesClick to read tutorial: Java try, catch, finally and Multi-catch with Examples throw vs throws in Java with ExamplesClick to read tutorial: throw vs throws in Java with Examples Creating Custom Exceptions in Java with ExamplesClick to read tutorial: Creating Custom Exceptions in Java with Examples Java Math Class: Essential Methods with ExamplesClick to read tutorial: Java Math Class: Essential Methods with Examples