Best Practices for Exception Handling in Java
This article presents nine essential Java exception handling best practices, including using finally blocks or try‑with‑resources for resource cleanup, specifying precise exception types, documenting throws clauses, providing descriptive messages, catching specific exceptions first, avoiding catching Throwable, not ignoring exceptions, and correctly wrapping exceptions without losing the original cause.
Handling exceptions in Java is not a simple task; even experienced developers need to consider which exceptions to handle and how to handle them.
Most development teams establish their own rules for exception handling, and these rules often differ significantly between teams.
This article presents several widely adopted best practices for Java exception handling.
1. Clean up resources in a finally block or use try-with-resources
A common mistake is to close resources such as InputStream at the end of the try block. If an exception occurs before the explicit close, the resource remains open.
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);
}
}2. Specify concrete exceptions
Declare the most specific exception types in method signatures to improve code readability.
public void doNotDoThis() throws Exception {
...
}
public void doThis() throws NumberFormatException {
...
}For example, NumberFormatException clearly indicates a numeric format error.
3. Document exceptions
When a method declares thrown exceptions, also document them in Javadoc to give callers detailed information.
/**
* This method does something extremely useful ...
*
* @param input
* @throws MyBusinessException if ... happens
*/
public void doSomething(String input) throws MyBusinessException {
...
}4. Include descriptive information when throwing exceptions
Provide concise, precise messages that describe the problem, supplementing the exception type.
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}If the exception name is not self‑explanatory, add a short descriptive message.
5. Catch the most specific exception first
Catching a generic exception before a specific one prevents the specific catch block from ever executing.
public void catchMostSpecificExceptionFirst() {
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e);
}
}6. Do not catch Throwable
Throwable includes both exceptions and errors; catching it can swallow unrecoverable JVM errors. Only catch it if you are absolutely sure you can handle it.
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}7. Do not ignore exceptions
Never leave a catch block empty; at minimum, log the exception so that unexpected issues can be diagnosed.
public void logAnException() {
try {
// do something
} catch (NumberFormatException e) {
log.error("This should never happen: " + e);
}
}8. Do not log and rethrow the same exception
Logging an exception and then rethrowing it results in duplicate log entries. Instead, either log at a higher level or wrap the exception in a custom one.
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}When more context is needed, wrap the original exception:
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}9. Preserve the original exception when wrapping
Always pass the caught exception as the cause when creating a custom exception; otherwise, valuable stack‑trace information is lost.
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}In summary, careful handling of throwing and catching exceptions improves code readability, maintainability, and debuggability. Discussing these best practices within a team and establishing consistent guidelines helps everyone communicate errors effectively.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.