Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Checked and unchecked exceptions

Java splits exceptions into two kinds. The compiler forces you to handle checked exceptions in advance, otherwise the program won't compile. Unchecked exceptions can be left unhandled — the compiler says nothing about them. This is specific to Java: most other languages have no such split.

Which is which

The rule is simple:

The Files.readString method reads a file and may throw IOException. That's why the compiler won't let you call it without handling:

void main() {
    String text = Files.readString(Path.of("notes.txt"));
    IO.println(text);
}

Output:

Main.java:2: error: unreported exception IOException; must be caught or declared to be thrown
    String text = Files.readString(Path.of("notes.txt"));
                                  ^
1 error
error: compilation failed

Option 1: catch it

The first way to satisfy the compiler is to wrap the call in try-catch:

void main() {
    try {
        String text = Files.readString(Path.of("notes.txt"));
        IO.println(text);
    } catch (IOException e) {
        IO.println("Could not read the file: " + e.getMessage());
    }
}

Output:

Could not read the file: notes.txt

There is no notes.txt file, so catch ran. The IOException message here is just the file name.

Option 2: declare throws

The second way is to state honestly in the method declaration that it may throw the exception. The throws keyword does this. Then whoever calls the method handles the error:

String readNotes() throws IOException {
    return Files.readString(Path.of("notes.txt"));
}
 
void main() {
    try {
        IO.println(readNotes());
    } catch (NoSuchFileException e) {
        IO.println("No such file: " + e.getMessage());
    } catch (IOException e) {
        IO.println("Some other I/O error");
    }
}

Output:

No such file: notes.txt

NoSuchFileException is a subclass of IOException, so it can be caught separately. You can put throws on main too — then on an error the program simply ends with a stack trace.

Unchecked exceptions

For RuntimeException and its subclasses the compiler requires nothing. A method can throw them without any throws:

int percent(int part, int total) {
    if (total <= 0) {
        throw new IllegalArgumentException("total must be greater than zero");
    }
    return part * 100 / total;
}
 
void main() {
    IO.println(percent(3, 4) + "%");
}

Output:

75%

Unchecked exceptions are usually not caught; instead the code is fixed so they don't happen: check for null, array bounds, input data.

Wrapping in an unchecked exception

Sometimes a checked exception can't be passed on — for example, inside a lambda, which can't declare throws. Then it gets wrapped in an unchecked one. For IOException there is a ready-made class, UncheckedIOException:

void main() {
    List<String> names = List.of("a.txt", "b.txt");
 
    try {
        names.forEach(name -> {
            try {
                IO.println(Files.readString(Path.of(name)));
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        });
    } catch (UncheckedIOException e) {
        IO.println("Error: " + e.getCause().getClass().getSimpleName());
    }
}

Output:

Error: NoSuchFileException

The original exception is kept as the cause and is available through getCause().

Common mistake

An empty catch written only to silence the compiler hides the error:

void main() {
    String text = "";
    try {
        text = Files.readString(Path.of("config.txt"));
    } catch (IOException e) {
    }
    IO.println("Settings: [" + text + "]");
}

Output:

Settings: []

The program went on with empty settings, and nobody found out why. If there is nothing sensible to do on the spot, add throws and let the calling code deal with it — and in a catch at least print the message.

Thanks for reading!

Donate to the site — we work hard for you.