Exception handling best practices

These Java Exception handling best practices provide software developers guidance when they develop new applications.

Be careful what you log

Software developers must always be mindful of the customer’s security and privacy rights.

One can move data logged by a Java application to various low-cost storage system, to be viewed by developers and administrators as they attempt to solve a bug.

However, you must ensure no protected data is written in the log files. Otherwise, your company will be out of compliance, and you might be out of a job.

Don't swallow thrown exception

Don’t catch an exception and then do nothing with it. That’s known as burying an exception, and it is definitely not a Java exception handling best practice.

At the very least, log the name of the exception and the message associated with it. That way you or others can extract information about an exceptional situation from the logs.

Swallow exceptions makes troubleshooting Java applications extremely hard.

try {
  readFile();
} catch (IOException ex) {
  log.warning("Error while reading file");
}

// OR

try {
  readFile();
} catch (IOException ex)
  throw new SystemException("System error");
}

Use a global Exception handler

There will always be uncaught RuntimeExceptions that creep into your code.

Always include a global Exception handler to deal with any uncaught exceptions. This will not only enable you to log and potentially handle the Exception, but it will also stop your app from crashing when a runtime exception is thrown.

If you're using SpringBoot Framework, you can read more at Error Handling for REST with Spring.

Explicitly define exceptions in the throws clause

Lazy developers use the generic Exception class in the throws clause of a method. Doing so is not a Java exception handling best practice.

Instead, always explicitly state the exact set of exception a given method might throw. This allows other developers to know the various error handling routines they can employ if a given method fails to execute properly.

Example:

void createOrder(OrderDto dto) {
  try {
    Product product = getProductInfo(dto.getProdId());
    // do something
  } catch (RuntimeException ex) {
    // Don't know exactly where the problem is to take a fallback step
  }
}

Product getProductInfo(Long prodId) {
  if (prodId == null) {
    throw new RuntimeException("Product Id must not be null.");
  }
  WebClient webClient = WebClient.builder().build();
  if (webClient == null) {
    throw new RuntimeException("Missing configuration for WebClient");
  }
  try {
    return webClient.call("https://api.gpcoder.com/products/" + prodId);
  }  catch (Exception ex) {
    if (isServer5xxError(ex)) {
      throw new RuntimeException("External server error", ex);
    }
    throw new RuntimeException("Unknown error", ex);
  }
}

Instead of using RuntimeException, we can define some of specific exception like IllegalArgumentException, MissingConfigurationException, ExternalServerError, InternalServerError, ...

Catch the most specific exception first

This is more of a compiler requirement than a Java exception handling best practice, but a developer should always catch the most specific exception first, and the least specific one last.

If this rule is not followed, the JVM will generate a compile time error with a fairly cryptic error message that is difficult to understand.

Make your software development life easier, and always catch the most specific exception in your code first.

Use modern exception handling techniques

Java has added many features around error and exception handling that simplify development and greatly reduce the verbosity of Java code.

Take advantage of the ability to catch multiple exceptions in a single catch block, automatically close resource with the try-with-resources block, and use RuntimeExceptions so other developers aren’t forced to handle the exceptions you throw.

Don’t close resources manually

Another important Java exception handling best practice is to allow the JVM to call the close() method of closeable resources. Don’t close resources yourself.

This is easily accomplished if you initialize resources within a try-with-resources block. The following example shows the try-with-resources semantics in action:

BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("D:\\gpcoder.txt"));
    /* do something */
} catch (IOException e) {
    /* do something */
} finally {
    try {
        if (br != null) {
            br.close();
        }
    } catch (IOException ex) {
        /* do something */
    }
}
try (BufferedReader br = new BufferedReader(new FileReader("D:\\gpcoder.txt"));) {
    /* do something */
} catch (IOException e) {
    /* do something */
} finally {
    /* do something */
}
 /* The JVM closes the resource after the code block */

The JVM closes the resource for you when the try…catch block completes. This eliminates the potential for messy and difficult-to-troubleshoot resource leaks.

Throw early and handle exceptions late

As soon as an exception condition happens in your code, throw an Exception. Don’t wait for any additional lines of code to run before you terminate execution of the method you are in.

The function to catch exceptions should go toward the end of a method. This puts fewer catch blocks in your methods, and makes your code much easier to read and maintain.

Read more: Fail Fast principle

Don’t log and re-throw

When an Exception occurs you should either, never do both.

  • Or log the Exception and carry on with your application.

  • Or re-throw the Exception and let another method log the details.

try {
  readFile();
} catch (IOExceptoin ex) {
  log.warning("Error while reading file", ex);
  throw ex;
}
try {
  readFile();
} catch (IOException ex) {
  log.warning("Error while reading file", ex);
}

// OR

try {
  readFile();
} catch (IOException ex) {
  throw new SystemException("System error", ex);
}

Check for suppressed exceptions

The suppressed exception is a relatively new language feature that not all developers are aware of.

Basically, the introduction of the try-with-resources function, made it possible to throw two exceptions at the same time. You can easily check this situation simply by querying for the suppressed exception’s existence, as is done in the example below:

try (BufferedReader br = new BufferedReader(new FileReader("D:\\gpcoder.txt"));) {
    /* do something */
} catch (IOException e) {
  System.out.println("Primary Exception:  " + e.getClass());
  if (e.getSuppressed().length > 0) {
    System.out.print("Suppressed Exception: " + e.getSuppressed()[0]);
  }
} finally {
    /* do something */
}

The only way to know if this scenario has occurred is to check if the target Exception also contains a suppressed exception.

Follow error handling clean code

Last updated