Master Java Exception Handling: 10 Best Practices Every Developer Should Follow
This article explains why exception handling in Java is challenging and presents ten practical best‑practice guidelines—including using finally or try‑with‑resources, declaring specific exceptions, documenting throws, providing clear messages, catching specific exceptions first, avoiding Throwable, logging properly, and preserving original causes—to write more robust and maintainable code.
Handling exceptions in Java is not trivial; even experienced developers spend considerable time deciding which exceptions to handle and how.
Most development teams establish their own rules for exception handling, which often differ significantly.
The following are ten widely adopted best practices for handling exceptions in Java.
Clean resources in a finally block or use try‑with‑resources
A common mistake is closing resources like InputStream at the end of the try block. If an exception occurs, the close call is skipped, leading to resource leaks.
public void doNotCloseResourceInTry() {
FileInputStream inputStream = null;
try {
File file = new File("./tmp.txt");
inputStream = new FileInputStream(file);
// use the inputStream to read a file
// do NOT do this
inputStream.close();
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}The proper approach is to place cleanup code in a finally block or use the try‑with‑resources statement.
public void closeResourceInFinally() {
FileInputStream inputStream = null;
try {
File file = new File("./tmp.txt");
inputStream = new FileInputStream(file);
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error(e);
}
}
}
}
public void automaticallyCloseResource() {
File file = new File("./tmp.txt");
try (FileInputStream inputStream = new FileInputStream(file)) {
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}Declare the most specific exception
Use the most specific exception type in method signatures to improve readability.
public void doNotDoThis() throws Exception {
...
}
public void doThis() throws NumberFormatException {
...
}For example, NumberFormatException clearly indicates a numeric formatting error.
Document thrown exceptions
When a method declares a thrown exception, add Javadoc with a @throws tag describing the scenario.
/**
* Performs an operation.
* @param input the input value
* @throws MyBusinessException if a business rule is violated
*/
public void doSomething(String input) throws MyBusinessException {
...
}Include descriptive information when throwing
Provide a concise description (one or two sentences) with the exception to aid logging and monitoring.
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
} NumberFormatExceptionalready conveys the error type; additional context should be brief.
Catch the most specific exception first
Catching a generic exception before a specific one prevents the specific handler from ever executing.
public void catchMostSpecificExceptionFirst() {
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e);
}
}Do not catch Throwable
Throwableincludes both Exception and Error. Catching it can swallow unrecoverable JVM errors; avoid it unless absolutely necessary.
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}Never ignore exceptions
Empty catch blocks hide problems. At a minimum, log the exception.
public void doNotIgnoreExceptions() {
try {
// do something
} catch (NumberFormatException e) {
// this will never happen
}
}
public void logAnException() {
try {
// do something
} catch (NumberFormatException e) {
log.error("This should never happen: " + e);
}
}Do not log and re‑throw the same exception
Logging an exception and then re‑throwing it results in duplicate log entries.
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}When wrapping, preserve the original cause
Wrap standard exceptions in custom ones to add context, but always pass the original exception as the cause.
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}Summary
Effective exception handling requires careful consideration of resource cleanup, specificity, documentation, messaging, ordering of catch blocks, and proper use of wrapping. Following these practices improves code readability, API usability, and overall system reliability.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
