Things go wrong even in correct programs: a file might be missing, a network call might time out, or a calculation might divide by zero. An exception is how Java signals that one of these problems occurred, and handling it properly is what keeps a single failure from crashing the entire program.

The try-catch block

try {
  int result = 10 / divisor;
} catch (ArithmeticException e) {
  System.out.println("Cannot divide by zero");
}

Risky code goes inside try. If it throws an exception, execution jumps immediately into the matching catch block instead of crashing the program, and everything after the failing line inside try is skipped.

The finally block

try {
  // open a file
} catch (Exception e) {
  // handle error
} finally {
  // close the file
}

Code inside finally always runs, whether or not an exception occurred, making it the natural place for cleanup work such as closing a file or a network connection that must happen no matter what.

Try-with-resources

For anything that implements Closeable, such as file streams, a try-with-resources block closes it automatically once the block finishes, removing the need for a manual finally block entirely:

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
  String line = reader.readLine();
} catch (IOException e) {
  System.out.println("Could not read the file");
}

Checked versus unchecked exceptions

Checked exceptions, like IOException, must be declared or caught, and the compiler enforces this. Unchecked exceptions, which extend RuntimeException, such as ArithmeticException or NullPointerException, are not enforced by the compiler and usually point to a programming mistake rather than an unavoidable external failure.

Catching more than one type

try {
  // risky code
} catch (IOException | SQLException e) {
  System.out.println("A recoverable error occurred: " + e.getMessage());
}

Writing a custom exception

Applications often define their own exception types to represent specific problems clearly, rather than reusing a generic one:

public class InsufficientFundsException extends RuntimeException {
  public InsufficientFundsException(String message) {
    super(message);
  }
}

Catching the right thing

Catching a specific exception type allows a program to respond appropriately to each distinct problem. Catching a broad Exception hides the real cause and makes debugging much harder later, since it treats every possible failure, no matter how different, as the exact same case.

Do not ignore errors

An empty catch block silently swallows a real problem and makes bugs almost impossible to trace afterward. At an absolute minimum, an error should be logged so there is a record that it happened, even if the program can otherwise continue safely.