java-manejo-excepciones-try-catch-resources

Exception Handling: try, catch, and try-with-resources

  • 4 min

The exception handling allows us to intercept a failure, respond to it, and release associated resources.

In the previous article we saw that an exception propagates through the call stack until it finds a block capable of handling it. If no one does, the affected thread terminates.

Today we are going to learn how to set up the safety net. We will see how to catch, manage, and recover from errors. And, very importantly, how to clean up the mess (close files or connections) no matter what happens.

The basic structure: try and catch

The mechanism is simple. We “try” (try) to execute dangerous code. If it fails, we immediately jump to the “catch” block (catch) to handle the crisis.

try {
    // Dangerous code
    int a = 10;
    int b = 0;
    int result = a / b; // This blows up (ArithmeticException)

    System.out.println("This will NEVER be printed");

} catch (ArithmeticException e) {
    // Error handling code
    System.out.println("Error! You cannot divide by zero.");
    // We can see technical details of the error:
    // e.printStackTrace();
}

System.out.println("The program continues running...");
Copied!

Catching multiple exceptions

Sometimes, code can fail for several reasons (file not found, incorrect format, network error…). We can put several catch blocks in order of specificity.

Order matters: You must catch first the most specific exceptions (children) and then the generic ones (parents). If you put catch (Exception e) first, it will swallow all errors and the blocks below will give a compilation error (unreachable code).

try {
    openFile();
    readNumber();
} catch (FileNotFoundException e) {
    System.out.println("File not found.");
} catch (NumberFormatException e) {
    System.out.println("The file does not contain a valid number.");
} catch (Exception e) {
    System.out.println("An unexpected error has occurred: " + e.getMessage());
}
Copied!

Since Java 7, if you are going to do the same thing for multiple errors, you can use Multi-catch with the vertical bar |:

catch (FileNotFoundException | NumberFormatException e) {
    System.out.println("Error reading the file (does not exist or wrong format).");
}
Copied!

The finally block: “no matter what”

Imagine you open a database connection in the try.

  • If everything goes well, you want to close it.
  • If there is an error, you also want to close it.

If you put close() inside the try, and it fails before reaching it, the connection remains open (Memory Leak). That is what finally is for. This block is executed when leaving the try or catch, even if there is a return. It cannot be guaranteed upon an abrupt JVM termination, such as System.exit, a process crash, or a system shutdown.

Scanner sc = null;
try {
    sc = new Scanner(new File("data.txt"));
    // Read data...
} catch (FileNotFoundException e) {
    System.out.println("Error");
} finally {
    // MANDATORY CLEANUP
    System.out.println("Closing resources...");
    if (sc != null) {
        sc.close();
    }
}
Copied!

try-with-resources

The previous code with finally is correct, but it is verbose, ugly, and error-prone (what if sc.close() throws another exception?).

To solve this, Java 7 introduced Try-with-Resources. It is a special syntax where we declare the resources inside the parentheses of the try.

Any object that implements the AutoCloseable interface (like Scanner, FileInputStream, database connections) will be closed automatically at the end of the block, whether it finishes successfully or not.

// Notice the parentheses after 'try'
try (Scanner sc = new Scanner(new File("data.txt"))) {

    while (sc.hasNext()) {
        System.out.println(sc.nextLine());
    }
    // NO NEED FOR finally OR close()!
    // Java calls sc.close() automatically here.

} catch (FileNotFoundException e) {
    System.out.println("File not found");
}
Copied!

Use try-with-resources when working with AutoCloseable resources, such as I/O streams, database connections, or sockets. It is cleaner and prevents system resource leaks.

Bad practices (anti-patterns)

There are two things beginners do that really annoy senior engineers.

Swallowing exceptions

try {
    doSomethingDangerous();
} catch (Exception e) {
    // Empty. I do nothing.
}
Copied!

This is terrible. The error occurs, the program continues, and you are none the wiser. When the program later fails because of this, finding the origin will be impossible. At the very least, print the error.

Generalized panic

try {
    // My entire program
} catch (Exception e) {
    System.out.println("Something happened");
}
Copied!

Do not wrap your entire main in a giant try-catch block. Try to isolate errors where they occur so you can react appropriately to each one.