Skip to content
Java exceptions 7 min read

try-catch Block

When something goes wrong at runtime — a file is missing, a number is divided by zero, or a network connection drops — Java throws an exception. The try-catch block is your first line of defense: it lets you wrap risky code and respond gracefully when things don’t go as planned, instead of letting the entire program crash.

Why You Need try-catch

Without exception handling, any runtime error terminates your program with a stack trace and leaves the user staring at a confusing error message. The try-catch construct lets you separate the “happy path” code from the “something went wrong” code, keeping both readable and your application alive.

Consider this crash without a try-catch:

public class NoCatch {
    public static void main(String[] args) {
        int result = 10 / 0; // throws ArithmeticException
        System.out.println(result); // never reached
    }
}

Output:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at NoCatch.main(NoCatch.java:3)

The JVM prints the stack trace and exits. Wrapping that code in a try-catch changes everything.

Basic Syntax

try {
    // code that might throw an exception
} catch (ExceptionType e) {
    // code that runs only if the exception occurs
}
  • The try block contains the code you want to guard.
  • The catch block specifies which exception type to intercept and what to do about it.
  • If no exception is thrown, the catch block is skipped entirely.

Your First try-catch

public class BasicTryCatch {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Caught: " + e.getMessage());
        }
        System.out.println("Program continues normally.");
    }
}

Output:

Caught: / by zero
Program continues normally.

Notice that execution continues after the catch block — the program does not crash. This is the key benefit of exception handling.

The Exception Object

The variable e in catch (ArithmeticException e) is an instance of the exception class. You can call several useful methods on it:

MethodWhat it returns
e.getMessage()Short human-readable description of the error
e.toString()Class name + message
e.printStackTrace()Full stack trace printed to stderr
e.getClass().getName()Fully qualified exception class name
e.getCause()The original cause (for wrapped exceptions)
try {
    String text = null;
    int len = text.length(); // NullPointerException
} catch (NullPointerException e) {
    System.out.println("Message : " + e.getMessage());
    System.out.println("Class   : " + e.getClass().getName());
    e.printStackTrace(); // useful during debugging
}

Tip: During development, e.printStackTrace() is your best friend. In production, log the exception using a logging framework like SLF4J/Logback instead of printing directly.

Checked vs Unchecked Exceptions

Java divides exceptions into two categories, and understanding the difference tells you when you are forced to use try-catch.

CategoryBase classMust handle?Examples
CheckedException (not RuntimeException)Yes — compiler enforces itIOException, SQLException, ClassNotFoundException
UncheckedRuntimeExceptionNo — optional but recommendedNullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException
ErrorErrorRarely — usually unrecoverableOutOfMemoryError, StackOverflowError

For checked exceptions, the compiler will refuse to compile your code unless you either handle them with try-catch or declare them with throws.

import java.io.*;

public class CheckedExample {
    public static void main(String[] args) {
        try {
            FileReader fr = new FileReader("notes.txt"); // checked: FileNotFoundException
            int ch = fr.read(); // checked: IOException
            System.out.println((char) ch);
            fr.close();
        } catch (FileNotFoundException e) {
            System.out.println("File not found: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("I/O error: " + e.getMessage());
        }
    }
}

Note: FileNotFoundException is a subclass of IOException. Always catch the more specific exception first — if you put IOException before FileNotFoundException, the compiler will complain that the second catch is unreachable.

Catching Multiple Exceptions (Multi-catch)

Since Java 7 you can catch multiple unrelated exception types in a single catch block using the pipe (|) operator. This avoids duplicating handling code.

public class MultiCatch {
    public static void main(String[] args) {
        try {
            String[] names = {"Alice", "Bob"};
            System.out.println(names[5]); // ArrayIndexOutOfBoundsException
            int x = Integer.parseInt("abc"); // NumberFormatException
        } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
            System.out.println("Caught: " + e.getClass().getSimpleName());
        }
    }
}

Note: In a multi-catch block, the variable e is implicitly final — you cannot reassign it inside the block.

For more patterns with multiple catch blocks, see Multiple catch Blocks.

The try-catch-finally Pattern

You can optionally add a finally block that always runs whether or not an exception was thrown — perfect for releasing resources like file handles or database connections.

import java.io.*;

public class TryCatchFinally {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            fr = new FileReader("data.txt");
            System.out.println("File opened.");
        } catch (FileNotFoundException e) {
            System.out.println("File not found.");
        } finally {
            System.out.println("Finally block always runs.");
            if (fr != null) {
                try { fr.close(); } catch (IOException ignored) {}
            }
        }
    }
}

Java 7 introduced try-with-resources, which automatically closes any object that implements AutoCloseable. This is the modern, preferred approach and eliminates the boilerplate of the finally-based cleanup above.

import java.io.*;

public class TryWithResources {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Error: " + e.getMessage());
        }
        // br is closed automatically here, even if an exception occurs
    }
}

Tip: Prefer try-with-resources over manual finally cleanup whenever you’re working with streams, connections, or any Closeable — it’s cleaner and less error-prone.

Common Mistakes

1. Catching Exception or Throwable too broadly

// Bad — swallows every possible error, makes debugging hard
try {
    riskyOperation();
} catch (Exception e) {
    // silently ignoring it is even worse
}

Catch the most specific exception type you can handle. Broad catches hide bugs.

2. Empty catch blocks (swallowing exceptions)

An empty catch block is one of the most dangerous patterns in Java. At minimum, log the error:

} catch (IOException e) {
    logger.error("Failed to read config file", e); // always log!
}

3. Catching and re-throwing without wrapping

If you catch an exception just to re-throw it unchanged, you add no value. Either handle it, wrap it in a more meaningful exception, or declare it with throws and let the caller decide.

Under the Hood

When the JVM executes a method, it associates the bytecode with an exception table — a data structure stored in the .class file alongside the method’s bytecode. Each entry in the table records:

  • A range of bytecode offsets (the try block’s instructions)
  • The target offset to jump to (the catch block’s first instruction)
  • The exception class to match

When an exception is thrown, the JVM walks up the call stack looking for a frame whose exception table has a matching entry for the current bytecode offset and exception type. This walk is called stack unwinding. If no matching handler is found after unwinding the entire stack, the JVM invokes the thread’s uncaught exception handler (defaulting to printing the stack trace and terminating).

Because exception table lookups only happen when an exception is actually thrown, the normal (no-exception) execution path carries zero overhead from try-catch. The JIT compiler treats the try block as regular code. This is why it’s safe to use try-catch liberally for truly exceptional conditions — it costs nothing when things go right.

Warning: Do NOT use exceptions for flow control (e.g., catching NumberFormatException to detect if a string is a number in a tight loop). Throwing and catching an exception involves filling in the stack trace, which is expensive. Use Integer.parseInt with a prior check or a utility method instead.

You can inspect the exception table of any compiled class with the javap tool:

javap -c -verbose TryCatchFinally.class

Look for the Exception table: section in the output — it lists exactly which bytecode ranges are protected and where control jumps on each exception type.

Last updated June 13, 2026
Was this helpful?