JavaException Handlingbeginner
Updated:

Exception Handling in Java: try, catch, finally, throw

6 min read

How Java reacts when things go wrong at runtime, and how try, catch, finally, throw and throws let you handle failures without crashing the program.

TL;DR – Quick Answer

Exception handling in Java is the mechanism for responding to runtime problems without crashing the program. You wrap risky code in a try block, handle specific failures in catch blocks, run cleanup in finally, raise exceptions yourself with throw, and declare them on a method signature with throws.

On This Page

Every Java program eventually hits a situation it did not plan for: a file that is missing, a number formatted wrongly, an array index that does not exist. When that happens, the JVM creates an exception object and starts unwinding the call stack looking for code that can deal with it. If nothing does, the thread dies with a stack trace. Exception handling is the set of keywords — try, catch, finally, throw, throws — that let you intercept that object and respond sensibly instead of crashing.

What happens when an exception is thrown

Picture a method call chain: main() calls readConfig(), which calls parsePort(). If parsePort() receives the text "abc" and calls Integer.parseInt(), a NumberFormatException object is created at that exact line. The JVM then asks: does parsePort() have a matching catch? No? Then it abandons parsePort() and asks readConfig(). Still no? It asks main(). This walk back up the chain is called stack unwinding, and the printed record of it is the stack trace you see in the console.

Two things follow from this model. First, an exception is a normal Java object — it carries a message, a cause, and the stack trace. Second, you get to choose where in the chain to handle it. Handling it too early often means you cannot do anything useful; letting it travel to a layer that understands the business context usually produces better error messages.

The exception hierarchy at a glance

All throwable types descend from java.lang.Throwable, using plain inheritance:

Type Parent Should you catch it?
Error (e.g. OutOfMemoryError) Throwable No — the JVM is in trouble
Exception (e.g. IOException) Throwable Yes — checked, compiler forces handling
RuntimeException (e.g. NullPointerException) Exception Usually fix the bug instead

The split between checked and unchecked types matters enough that interviewers treat it as its own question — the full breakdown is in checked vs unchecked exceptions. For now: checked exceptions must be caught or declared, unchecked ones may simply propagate.

try, catch and finally in action

Here is the complete structure in one runnable program:

public class BankWithdrawal {
    public static void main(String[] args) {
        double balance = 5000.0;
        double requested = 8000.0;
        try {
            if (requested > balance) {
                throw new IllegalArgumentException(
                    "Requested " + requested + " but balance is " + balance);
            }
            balance -= requested;
            System.out.println("Withdrawal successful");
        } catch (IllegalArgumentException e) {
            System.out.println("Withdrawal failed: " + e.getMessage());
        } finally {
            System.out.println("Closing ATM session for this customer");
        }
        System.out.println("Program continues normally");
    }
}

Run it and notice the order of output: the catch block prints the failure, finally prints the session message, and then the program continues past the whole construct. Change requested to 3000.0 and finally still runs — that is the whole point. finally is where cleanup belongs: closing connections, releasing locks, deleting temp files.

Common mistake: A common mistake beginners make is putting a return inside finally. That return silently swallows any exception thrown in try or catch, which makes bugs almost impossible to trace. Keep finally for cleanup only, never for control flow.

Multiple catch blocks and multi-catch

When a try block can fail in more than one way, stack catch blocks from most specific to most general. Since Java 7 you can also combine unrelated types in one block with |:

public class ConfigReader {
    public static void main(String[] args) {
        String[] input = {"8080"};
        try {
            String raw = input[1];               // may throw AIOOBE
            int port = Integer.parseInt(raw);    // may throw NFE
            System.out.println("Port: " + port);
        } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
            System.out.println("Bad config, using default port 8080");
            System.out.println("Reason: " + e);
        }
    }
}

The multi-catch keeps the handling logic in one place when the recovery action is identical. One rule the compiler enforces: a subclass catch cannot appear after its parent, because the parent would already have caught everything — catch (Exception e) followed by catch (IOException e) is a compile error.

Interview note: In a multi-catch block the variable e is implicitly final. You cannot reassign it, and its static type is the closest common superclass of the listed exceptions. This exact detail shows up in written screening tests.

throw vs throws: two keywords, two jobs

throw raises an exception right now. throws is a declaration on the method signature that warns callers a checked exception may come out. Freshers mix these up constantly, so keep the grammar straight: you throw an object, a method throws a type.

import java.io.FileNotFoundException;

public class LicenseChecker {
    static void validate(String path) throws FileNotFoundException {
        if (path == null || path.isBlank()) {
            throw new FileNotFoundException("License path is empty");
        }
        System.out.println("License found at " + path);
    }

    public static void main(String[] args) {
        try {
            validate("");
        } catch (FileNotFoundException e) {
            System.out.println("Handled: " + e.getMessage());
        }
    }
}

Because FileNotFoundException is checked, validate() must either handle it or declare it with throws — and once declared, main() is forced by the compiler to deal with it. That compiler pressure is the defining feature of checked exceptions.

try-with-resources: the modern cleanup

Before Java 7, closing a file safely required a clumsy finally block with its own nested try-catch. Try-with-resources fixes that: any object implementing AutoCloseable declared in the parentheses is closed automatically, in reverse order, even when an exception is thrown.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;

public class FirstLine {
    public static void main(String[] args) {
        String data = "line one\nline two";
        try (BufferedReader reader = new BufferedReader(new StringReader(data))) {
            System.out.println("Read: " + reader.readLine());
        } catch (IOException e) {
            System.out.println("Could not read: " + e.getMessage());
        }
        // reader is already closed here — no finally needed
    }
}

Notice there is no explicit close() call anywhere. If both the body and the close() throw, the body's exception wins and the close failure is attached as a suppressed exception — you can inspect it with e.getSuppressed().

Pro tip: Whenever a class name suggests an external resource — Connection, Stream, Reader, Socket — reach for try-with-resources first. In code reviews, a manual close() in 2026-era Java is treated as a defect, not a style choice.

Writing your own custom exception

Business rules deserve their own exception types. A custom exception is just a class extending Exception (checked) or RuntimeException (unchecked) with constructors that pass the message and cause upward:

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

    public InsufficientBalanceException(String message, Throwable cause) {
        super(message, cause);
    }
}

The second constructor matters more than beginners expect. When you catch a low-level exception and rethrow a business one, always pass the original as the cause — otherwise the real stack trace is lost and production debugging becomes guesswork. Wrapping without the cause is one of the most frequent findings when we review learner projects in the Java Full Stack program.

Best practices that come up in interviews

A few habits separate candidates who have written real Java from those who memorized syntax:

  • Catch specific types. catch (Exception e) at a low level hides programming errors like a NullPointerException from a bad String operation.
  • Never leave a catch block empty. At minimum, log the exception. A silent catch turns a ten-minute bug into a ten-day one.
  • Do not use exceptions for flow control. Checking list.isEmpty() is dramatically cheaper than catching IndexOutOfBoundsException, because filling in a stack trace is expensive.
  • Throw early, catch late. Validate inputs at the top of a method and throw immediately; handle exceptions in the layer that can actually explain the failure to a user or a log.
  • Mind other threads. An uncaught exception kills only its own thread — in a multithreaded program the rest of the application keeps running, often in a half-broken state, unless you install an UncaughtExceptionHandler.

Exception questions appear in almost every Java round, from fresher screenings to the scenario drills in Java interview questions for 2 years experience. If you can trace what happens to an exception object from throw to stack trace, and justify when to use finally versus try-with-resources, you are ahead of most candidates at this level.

Frequently Asked Questions

What is the difference between throw and throws in Java?
throw is a statement that actually raises an exception object inside a method, for example throw new IllegalArgumentException(). throws is part of a method signature that declares which checked exceptions the method might pass to its caller. One performs the action, the other announces the possibility.
Does finally always execute in Java?
Almost always. The finally block runs whether the try block completes normally, throws an exception, or even returns early. The only cases where it does not run are when the JVM exits first, for example via System.exit(), when the thread is killed, or when the JVM itself crashes.
Can a try block exist without a catch block?
Yes. A try block must be followed by at least one catch block or a finally block. try-finally without catch is valid, and try-with-resources can even stand alone because the compiler generates the cleanup code for you.
What happens if an exception is not caught in Java?
The exception propagates up the call stack, method by method, looking for a matching catch block. If it reaches the top of the thread without being handled, the thread terminates and the JVM prints the stack trace. In a single-threaded console program that usually means the whole program stops.
Is it a good practice to catch Exception directly?
Generally no. Catching Exception hides bugs because it also swallows unchecked exceptions like NullPointerException that indicate programming errors. Catch the most specific exception types you can actually handle, and let the rest propagate to a global handler that logs them.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 14 July 2026 LinkedIn
Chat with us