20 Essential Exception‑Handling Rules to Prevent Crashes in Java
This article presents twenty practical rules for handling exceptions in Java, covering why catching RuntimeException is discouraged, how to avoid using exceptions for control flow, proper use of finally blocks, logging best practices, custom exception design, and early input validation to keep applications stable and maintainable.
Exception handling is a critical part of Java programming; it affects stability, security, user experience, and resource utilization. The article lists twenty best‑practice rules for handling exceptions correctly, using Spring Boot 3.5.0 as the runtime environment.
1. Introduction
The article begins with an overview of common exception types in Java and states that the following sections will detail twenty best practices.
2.1 Avoid catching RuntimeException
Do not catch unchecked exceptions such as NullPointerException or IndexOutOfBoundsException. Prefer pre‑condition checks instead.
Correct example:
if (obj != null) {
// ...
}
System.out.println(a / b);Wrong example:
try {
obj.method();
} catch (Exception e) {
// swallow
}If a RuntimeException cannot be avoided (e.g., NumberFormatException), it should still be caught and handled.
2.2 Do not use exceptions for control flow
Exceptions are intended for unexpected error conditions, not regular logic. Using them for flow control incurs high performance cost, reduces readability, and violates design intent.
Performance overhead: throwing and catching is much slower than ordinary condition checks.
Poor readability: logic becomes obscure and hard to maintain.
Design violation: semantics of an exception are lost.
Wrong example:
public class BadSearch {
public static boolean contains(int[] arr, int target) {
try {
for (int i = 0; ; i++) {
if (arr[i] == target) {
return true;
}
}
} catch (ArrayIndexOutOfBoundsException e) {
// use exception to end loop!
return false;
}
}
}Correct example:
public class GoodSearch {
public static boolean contains(int[] arr, int target) {
for (int value : arr) {
if (value == target) {
return true;
}
}
return false; // normal termination, no exception
}
}2.3 Use finally correctly
Always release resources (files, DB connections) in a finally block, regardless of whether an exception occurs.
public class FinallyBlockExample {
public static void main(String[] args) {
FileInputStream file = null;
try {
file = new FileInputStream("someFile.txt");
int data = file.read();
while (data != -1) {
System.out.print((char) data);
data = file.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}Note: Java 7+ offers try‑with‑resources for more concise handling.
2.4 Do not return from finally
Returning inside finally overrides any return from the try block, potentially causing logic errors.
public static int notGood() {
try {
System.out.println("execute try block");
return 1; // this return is ignored
} finally {
System.out.println("execute finally block");
return 2; // final result is 2
}
}2.5 Do not ignore exceptions
Even if an exception seems unimportant, it should at least be logged.
public class IgnoreExceptionsBad {
public void doNotIgnoreExceptions() {
try {
int num = Integer.parseInt("pack_xg");
} catch (NumberFormatException e) {
// completely ignored, no log
}
}
}Correct handling logs the error and may rethrow:
public class LogExceptionsGood {
private static final Logger log = Logger.getLogger(LogExceptionsGood.class.getName());
public void logAnException() {
try {
int num = Integer.parseInt("pack_xg");
} catch (NumberFormatException e) {
log.error("Failed to parse number: " + e.getMessage());
throw e;
}
}
}2.6 Catch specific exception subclasses
Catching the most specific exception allows tailored handling.
public class BusinessService {
private static final Logger LOGGER = Logger.getLogger(BusinessService.class.getName());
public void processFile() {
try {
readFile("nonexistent.txt");
} catch (FileNotFoundException e) {
LOGGER.warning("File not found: " + e.getMessage());
} catch (IOException e) {
LOGGER.error("File read error: " + e.getMessage());
}
}
private void readFile(String path) throws FileNotFoundException, IOException {
if (!path.endsWith(".txt")) {
throw new FileNotFoundException("File not found");
}
throw new IOException("Read error");
}
}2.7 Provide context in exception logs
Include relevant parameters and exception type in log messages to aid debugging.
public class BusinessService {
private static final Logger LOGGER = Logger.getLogger(BusinessService.class.getName());
public void processData(String filePath, int retryCount) {
try {
readData(filePath);
} catch (IOException | SQLException e) {
LOGGER.error("Business processing failed [filePath: {}, retryCount: {}, errorType: {}]",
filePath, retryCount, e.getClass().getSimpleName(), e);
}
}
private void readData(String path) throws IOException, SQLException {
if (path == null) {
throw new IOException("File path cannot be null");
}
throw new SQLException("DB connection timeout");
}
}2.8 Use custom exceptions when built‑in ones are insufficient
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
public void doSomething() throws MyException {
throw new MyException("Specific error message");
}2.9 Preserve stack trace when rethrowing
When wrapping an exception, pass the original as the cause.
catch (NoSuchMethodException e) {
// wrong: loses original stack trace
throw new MyServiceException("Info: " + e.getMessage());
}
// correct
catch (NoSuchMethodException e) {
throw new MyServiceException("Business execution error", e);
}2.10 Prefer standard exceptions
public void setValue(int value) {
if (value < 0) {
throw new IllegalArgumentException("Invalid argument");
}
// ...
}2.11 Avoid printStackTrace() in production
Printing stack traces can leak sensitive data, degrade performance, and produce incomplete information in multi‑threaded environments. Use a logging framework (log4j, slf4j, logback) instead.
try {
// business code
} catch (Exception e) {
logger.error("An error occurred", e); // logs stack trace safely
}2.12 Do not catch Throwable
Catching Throwable also captures serious errors (e.g., OutOfMemoryError) that should not be handled.
public void doNotCatchThrowable() {
try {
// risky operation
} catch (Throwable t) {
// avoid this
}
}2.13 Use try‑with‑resources
Java 7+ can automatically close resources, eliminating manual finally cleanup.
try (FileInputStream input = new FileInputStream("file.txt")) {
int data = input.read();
while (data != -1) {
System.out.print((char) data);
data = input.read();
}
} catch (IOException e) {
// log with framework
}Multiple resources can be managed together:
try (FileInputStream input = new FileInputStream("input.txt");
FileOutputStream output = new FileOutputStream("output.txt")) {
byte[] buffer = new byte[1024];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
} catch (IOException e) {
e.printStackTrace();
}2.14 Provide detailed context when throwing
public void loadConfiguration(String path) throws IOException {
try {
// load logic
} catch (IOException e) {
throw new IOException("Failed to load configuration file, path: " + path, e);
}
}2.15 Do not log and then rethrow the same exception
Logging and rethrowing creates redundant output and can confuse error tracing.
// wrong
try {
// risky code
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
// correct – wrap with business exception
public void wrapException(String input) throws MyBusinessException {
try {
// risky code
} catch (NumberFormatException e) {
throw new MyBusinessException("Invalid input: " + input, e);
}
}2.16 Do not throw from finally
Throwing in finally masks the original exception.
try {
someMethod(); // may throw exceptionOne
} finally {
try {
cleanUp(); // may throw exceptionTwo
} catch (Exception e) {
log.error("Cleanup failed", e); // log only, do not rethrow
}
}2.17 Throw exceptions relevant to the method
Expose meaningful exceptions to callers instead of low‑level ones.
public static int divide(int a, int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("Division by zero");
}
return a / b;
}2.18 Validate user input early
Validate inputs before performing operations to catch errors early.
Connection conn = ...;
try {
conn.setAutoCommit(false);
validateUserInput();
insertUserData(conn);
validateAddressInput();
insertAddressData(conn);
conn.commit();
} catch (SQLException e) {
if (conn != null) {
try { conn.rollback(); } catch (SQLException ex) { System.err.println("Error: " + ex.getMessage()); }
}
System.err.println("Error: " + e.getMessage());
} finally {
if (conn != null) {
try { conn.close(); } catch (SQLException e) { System.err.println("Error: " + e.getMessage()); }
}
}2.19 Log an exception only once
In multithreaded environments, separate log statements can interleave; combine related messages into a single log entry.
// wrong
log.error("Error1");
log.error("Error2");
// correct
log.error("Error1, Error2…");2.20 Use try‑finally when you do not intend to handle the exception
If you only need cleanup and want the exception to propagate, omit the catch block.
try {
method1(); // may call method2 which throws
} finally {
cleanUp(); // always executed
}Conclusion
Effective Java exception handling follows these principles: avoid catching unchecked exceptions, never use exceptions for flow control, release resources in finally or via try‑with‑resources, log with a proper framework, preserve original stack traces, prefer standard or specific exceptions, provide rich contextual information, and validate inputs early. Following the twenty rules helps keep applications robust, readable, and maintainable.
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.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.
