Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a small utility recently that reads a "timeout" value from a config file and uses it to index into a predefined array of retry strategies. It's a simple task, but it's a minefield of potential exceptions. Let me show you the mess I started with.

try {
    String line = Files.readString(Path.of("config.txt")).trim();
    int timeout = Integer.parseInt(line);
    Strategy strategy = retryStrategies[timeout];
    System.out.println("Using strategy: " + strategy);
} catch (IOException e) {
    System.err.println("Config file missing or unreadable. Using default.");
    applyDefaultStrategy();
} catch (NumberFormatException e) {
    System.err.println("Config value isn't a number. Using default.");
    applyDefaultStrategy();
} catch (ArrayIndexOutOfBoundsException e) {
    System.err.println("Timeout value is out of range. Using default.");
    applyDefaultStrategy();
}

The Copy-Paste Fatigue

Looking at that, you probably notice the same thing I did: I'm repeating myself. Three different catch blocks, but they all essentially do the same thing—log a problem and fall back to a default strategy. It feels clunky. Whenever I see the same line of code appearing three times in a row, my internal "refactor alarm" goes off.

My first instinct might be to just catch the parent class. Since all of these are `Exceptions`, I could just do catch (Exception e). But that's a dangerous habit. If I catch Exception, I'm also catching NullPointerException or some other runtime disaster that I actually *want* the program to crash on so I can find the bug. Being too broad is how you hide critical failures in production.

Cleaning Up with the Pipe Operator

Java provides a much cleaner middle ground called the multi-catch block. Instead of writing separate blocks for every specific exception, we can group them using a single pipe symbol (|). Let's try rewriting my logic this way.

try {
    String line = Files.readString(Path.of("config.txt")).trim();
    int timeout = Integer.parseInt(line);
    Strategy strategy = retryStrategies[timeout];
    System.out.println("Using strategy: " + strategy);
} catch (IOException | NumberFormatException | ArrayIndexOutOfBoundsException e) {
    System.err.println("Configuration error encountered. Falling back to default.");
    applyDefaultStrategy();
}

Now the code is breathable. I'm still being explicit about which exceptions I'm willing to handle, but I've collapsed the redundant logic into one place. One thing to keep in mind here: the variable e in a multi-catch is implicitly final. You can't reassign it inside the block, though in most real-world scenarios, you wouldn't want to anyway.

A Small Catch on the Multi-Catch

While I was playing with this, I tried to add FileNotFoundException to that list, thinking I'd be even more specific. But the compiler stopped me immediately. It gave me an error saying that FileNotFoundException is already covered by IOException.

This is a key rule: you cannot list two exceptions in a multi-catch if one is a subclass of the other. Since FileNotFoundException extends IOException, listing both is redundant. Java forces you to pick one or the other to avoid ambiguity. If you want to handle a specific subclass differently, you have to give it its own catch block before the multi-catch block that handles the parent.

It's a small detail, but it's the kind of thing that will make you stare at your IDE for five minutes wondering why the code won't compile.




📋 Practical Task

Refactoring the Network Configuration Parser

You are reviewing a teammate's code for a network utility. They have written a method that parses a port number from a string and attempts to open a connection. The current implementation is repetitive and violates the DRY (Don't Repeat Yourself) principle.

Your Task: Refactor the following code to use a multi-catch block. Ensure that you only group exceptions that would logically result in the same recovery action (logging a "Connection Setup Failed" message and returning false).

public boolean setupConnection(String portStr) {
    try {
        int port = Integer.parseInt(portStr);
        if (port < 0 || port > 65535) {
            throw new IllegalArgumentException("Port out of range");
        }
        openSocket(port);
        return true;
    } catch (NumberFormatException e) {
        System.out.println("Connection Setup Failed: Invalid number format.");
        return false;
    } catch (IllegalArgumentException e) {
        System.out.println("Connection Setup Failed: Port value is illegal.");
        return false;
    } catch (IOException e) {
        System.out.println("Connection Setup Failed: Network error.");
        return false;
    } catch (NullPointerException e) {
        // This should NOT be caught in the multi-catch; 
        // let it propagate as a critical bug.
        throw e;
    }
}

Modify the code so that NumberFormatException, IllegalArgumentException, and IOException are handled by a single catch block, while keeping the NullPointerException behavior unchanged.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.