Error Handling in Java: From try/catch to Advanced Techniques
Exception handling is one of the cornerstones of a reliable Java application. Most developers are familiar with the basic try-catch-finally construct, but at an advanced level, Java offers much more flexible and efficient tools. In this article, we will cover not only the classic approach but also modern features such as multi-catch, try-with-resources, creating custom exceptions, and handling strategies. This material is intended for developers who already write confidently in Java and want to deepen their knowledge.
1. The Classic: try-catch-finally and Its Nuances
The basic construct remains the foundation. However, at an advanced level, it is important to understand how it works under the hood and what mistakes even experienced programmers make.
Consider an example with file input/output:
import java.io.*;
public class ClassicExample { public static void main(String[] args) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("file.txt")); String line = reader.readLine(); System.out.println(line); } catch (IOException e) { System.err.println("Read error: " + e.getMessage()); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { System.err.println("Close error: " + e.getMessage()); } } }}Key points for an advanced level:
- Order of catch blocks: more specific exceptions must come first. If you place
ExceptionbeforeIOException, the compiler will throw an error. - The finally block always executes, even if there is a
returnin try or catch. This is important for releasing resources. - Exception suppression: if an exception occurs in both try and finally, the exception from finally "swallows" the first one. To avoid this, use
Throwable.getSuppressed().
2. Multi-catch: Elegance and Readability
Starting with Java 7, it became possible to combine multiple exceptions in a single catch block. This significantly reduces code duplication.
Without multi-catch, the code looks cumbersome:
try { // code that may throw IOException or SQLException} catch (IOException e) { logger.error("I/O error", e); throw new RuntimeException(e);} catch (SQLException e) { logger.error("Database error", e); throw new RuntimeException(e);}With multi-catch, it becomes concise:
try { // code} catch (IOException | SQLException e) { logger.error("Resource error", e); throw new RuntimeException(e);}Important: the variable e in multi-catch is final (or effectively final). You cannot assign it another value. This is done for type safety.
3. Try-with-resources: Automatic Resource Management
This is perhaps the most powerful improvement for resource handling. It automatically closes resources that implement the AutoCloseable (or Closeable) interface.
Compare with the classic example above. Here is how it looks with try-with-resources:
import java.io.*;
public class TryWithResourcesExample { public static void main(String[] args) { try (BufferedReader reader = new BufferedReader(new FileReader("file.txt")); BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
Strin