Java throws Keyword

Su Suriya Ravichandran Updated 14 Nov 2026
10 min read ·Lesson 128 of 133

What throws Declares

The throws keyword appears in a method or constructor declaration. It lists the checked exceptions that the method may pass on to its caller instead of handling them itself.

returnType methodName(parameters) throws ExceptionType1, ExceptionType2 {
    // body
}
static String readConfig(Path file) throws IOException {
    return Files.readString(file);   // may throw IOException; we don't catch it here
}

throws IOException is part of the method's contract, just like its parameter and return types. It tells the compiler and every caller: "calling this method can fail with an IOException, and you must be prepared for that."

Why Java has it: for checked exceptions, the compiler enforces the catch-or-declare rule. Every checked exception that can occur in a method must be either caught there (with try...catch) or declared with throws. Declaring it moves the responsibility one level up. Each caller then faces the same choice — catch it or declare it — until some method decides to handle it. This guarantees that a checked exception can never be forgotten silently.

throw vs throws

The two keywords look alike but do completely different things:

throwthrows
What it isA statementA clause in a method/constructor signature
Where it appearsInside a method bodyAfter the parameter list
What follows itOne exception object: throw new IOException("...")One or more exception types: throws IOException, SQLException
What it doesActually throws an exception right nowDeclares that the method might let these exceptions escape
Needed forSignalling an errorChecked exceptions that the method doesn't catch

A method can declare throws IOException without containing any throw statement — the exception may come from a method it calls, as in readConfig above. And a method can throw an unchecked exception without any throws clause.

Propagating Through Application Layers

Real applications are organised in layers, and usually only the top layer knows how a failure should be presented. throws lets an exception travel from where it happens to where it can be handled sensibly.

import java.io.IOException;
import java.nio.file.*;
import java.util.*;

public class Layers {
    // Data layer: knows how to read files, not what to do when they are missing
    static List<String> readLines(Path file) throws IOException {
        return Files.readAllLines(file);
    }

    // Business layer: computes a total, still can't decide how to report a missing file
    static double totalSales(Path file) throws IOException {
        double total = 0;
        for (String line : readLines(file)) {
            total += Double.parseDouble(line.split(",")[1]);
        }
        return total;
    }

    // Presentation layer: decides what the user sees
    static void showReport(Path file) {
        try {
            System.out.printf("%s: total sales %.2f%n", file.getFileName(), totalSales(file));
        } catch (IOException e) {
            System.out.println(file.getFileName() + ": report unavailable (" + e.getClass().getSimpleName() + ")");
        }
    }

    public static void main(String[] args) throws IOException {
        Path dir = Files.createTempDirectory("sales");
        Path nov = Files.writeString(dir.resolve("nov.csv"), "pen,1250.50\nbag,3400.00\nink,980.25\n");
        showReport(nov);
        showReport(dir.resolve("dec.csv"));
    }
}

Output:

nov.csv: total sales 5630.75
dec.csv: report unavailable (NoSuchFileException)

How the exception travels for dec.csv:

  1. Files.readAllLines is declared throws IOException. The file doesn't exist, so it throws NoSuchFileException (a subclass of IOException).
  2. readLines doesn't catch it. Because its signature says throws IOException, this compiles, and the exception passes to totalSales.
  3. totalSales also declares throws IOException, so the exception passes on to showReport. The loop never runs.
  4. showReport calls totalSales inside try...catch (IOException e). Here the chain ends: the user sees a short message and the program continues with the next report.

If you delete throws IOException from totalSales, compilation fails with unreported exception IOException; must be caught or declared to be thrown at the call to readLines. The compiler checks every link in the chain.

showReport has no throws clause because it handles the exception. main declares throws IOException because createTempDirectory and writeString can fail during setup, and for a small demo it's acceptable to let such a failure end the program.

Declaring Several Exceptions

List multiple types separated by commas:

void syncInventory() throws IOException, InterruptedException {
    String data = Files.readString(Path.of("stock.csv")); // IOException
    Thread.sleep(500);                                   // InterruptedException
    // ...
}

A caller can catch them separately, with a multi-catch, or declare them in turn. You only need to declare checked exceptions. If you declare a parent type such as throws Exception, it covers every checked exception, but callers then have no idea what can actually go wrong and are forced to handle Exception in general. Declare the most specific types that are meaningful.

main with throws

import java.nio.file.*;

public class MainThrows {
    public static void main(String[] args) throws Exception {
        System.out.println(Files.readString(Path.of("app.yaml")));
    }
}

Output (when app.yaml doesn't exist):

Exception in thread "main" java.nio.file.NoSuchFileException: app.yaml
	at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)
	...
	at java.base/java.nio.file.Files.readString(Files.java:3318)
	at MainThrows.main(MainThrows.java:5)

main can declare exceptions like any method. There's no caller in your code above it, so an exception leaving main reaches the JVM, which prints the stack trace and exits with status 1. The java.base/... frames show where inside the JDK the failure originated; the last frame is your line. This is fine for quick scripts and learning examples, but in real applications main should catch failures and print a clear message (and often choose an exit code with System.exit).

throws Clauses and Unchecked Exceptions

You may list unchecked exceptions in a throws clause:

/**
 * Withdraws money from the account.
 *
 * @throws IllegalArgumentException if amount is not positive
 * @throws IllegalStateException if the account is frozen
 */
void withdraw(long amount) throws IllegalArgumentException, IllegalStateException { ... }

The compiler ignores unchecked types in throws — callers are not forced to handle them. The convention in the JDK and most libraries is to document unchecked exceptions with the Javadoc @throws tag and leave them out of the throws clause, keeping the clause for checked exceptions the caller must deal with.

throws on Constructors

Constructors can declare checked exceptions too. If a constructor throws, no object is created.

import java.io.*;
import java.nio.file.*;
import java.util.*;

public class Ctor {
    static class PriceList {
        private final Map<String, Double> prices = new HashMap<>();

        PriceList(Path file) throws IOException {
            for (String line : Files.readAllLines(file)) {
                String[] f = line.split("=");
                prices.put(f[0], Double.parseDouble(f[1]));
            }
        }

        double price(String sku) { return prices.getOrDefault(sku, 0.0); }
    }

    public static void main(String[] args) throws IOException {
        Path f = Files.writeString(Files.createTempFile("prices", ".txt"), "PEN=12.5\nINK=80\n");
        PriceList list = new PriceList(f);
        System.out.println(list.price("INK"));
    }
}

Output:

80.0

Every new PriceList(...) expression is subject to the catch-or-declare rule, exactly like a method call. Constructors that do I/O are generally best avoided — a static factory method such as PriceList.load(path) makes the possible failure more visible — but the rule is the same either way.

throws and Method Overriding

When a subclass overrides a method, it cannot declare new or broader checked exceptions than the method it overrides. The reason is substitutability: code written against the parent type has only prepared for the exceptions the parent declares.

import java.io.IOException;

public class Override1 {
    static class Exporter {
        void export(String data) { System.out.println(data); }
    }

    static class FileExporter extends Exporter {
        @Override
        void export(String data) throws IOException {
            throw new IOException("disk full");
        }
    }
}

Compiler output:

Override1.java:10: error: export(String) in FileExporter cannot override export(String) in Exporter
        void export(String data) throws IOException {
             ^
  overridden method does not throw IOException
1 error

Code holding an Exporter reference calls export without any try, because Exporter.export declares nothing. If a FileExporter were allowed to throw IOException there, that code would receive a checked exception it was never forced to handle.

What is allowed:

Overriding method declaresAllowed?
The same checked exceptionsYes
Fewer checked exceptions, or noneYes
Subclasses of the declared exceptions (narrower)Yes
New or broader checked exceptionsNo
Any unchecked exceptionsYes — the rule applies only to checked ones

So the design decision belongs to the parent type. If implementations will reasonably need to do I/O, the interface itself should declare it:

import java.io.*;
import java.nio.file.*;

public class Override2 {
    interface Exporter {
        void export(String data) throws IOException;
    }

    static class ConsoleExporter implements Exporter {
        @Override
        public void export(String data) {               // fewer exceptions: allowed
            System.out.println("console: " + data);
        }
    }

    static class FileExporter implements Exporter {
        private final Path target;
        FileExporter(Path target) { this.target = target; }

        @Override
        public void export(String data) throws NoSuchFileException {   // narrower subclass: allowed
            if (!Files.exists(target.getParent())) {
                throw new NoSuchFileException(target.getParent().toString());
            }
            System.out.println("file: " + data);
        }
    }

    public static void main(String[] args) {
        Exporter[] exporters = { new ConsoleExporter(), new FileExporter(Path.of("/no/such/dir/out.txt")) };
        for (Exporter ex : exporters) {
            try {
                ex.export("Q3 sales");
            } catch (IOException e) {
                System.out.println("export failed: " + e);
            }
        }
        new ConsoleExporter().export("no try needed");
    }
}

Output:

console: Q3 sales
export failed: java.nio.file.NoSuchFileException: /no/such/dir
console: no try needed
  • Through the Exporter interface, the compiler only sees throws IOException, so the loop must handle IOException — and that handler correctly catches the NoSuchFileException from FileExporter.
  • The last line calls export on a variable of type ConsoleExporter, whose method declares nothing. The compiler checks the static type of the reference, so no try is required there.

The JDK uses this same pattern: Runnable.run() declares no exceptions, while Callable.call() declares throws Exception, which is why a Callable passed to an ExecutorService can throw checked exceptions and a Runnable can't.

Checked Exceptions and Lambdas

The standard functional interfaces (Function, Consumer, Supplier, Predicate) don't declare any checked exceptions. Therefore a lambda implementing them can't throw one:

import java.nio.file.*;
import java.util.*;

public class Lambda1 {
    public static void main(String[] args) {
        List<Path> files = List.of(Path.of("a.txt"));
        files.stream().map(p -> Files.readString(p)).forEach(System.out::println);
    }
}

Compiler output:

Lambda1.java:7: error: unreported exception IOException; must be caught or declared to be thrown
        files.stream().map(p -> Files.readString(p)).forEach(System.out::println);
                                                ^
1 error

Adding throws IOException to main doesn't help, because the exception would escape from the lambda body, which is the implementation of Function.apply — and apply declares no exceptions (the overriding rule again).

The usual solution is to catch the checked exception inside a helper and rethrow it wrapped in an unchecked exception. The JDK provides java.io.UncheckedIOException for exactly this:

import java.io.*;
import java.nio.file.*;
import java.util.*;

public class Lambda2 {
    static String readUnchecked(Path p) {
        try {
            return Files.readString(p);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    public static void main(String[] args) throws IOException {
        Path dir = Files.createTempDirectory("notes");
        Path a = Files.writeString(dir.resolve("a.txt"), "first note");
        List<Path> files = List.of(a, dir.resolve("b.txt"));
        try {
            files.stream().map(Lambda2::readUnchecked).forEach(System.out::println);
        } catch (UncheckedIOException e) {
            IOException cause = e.getCause();
            System.out.println("stream stopped: " + cause.getClass().getSimpleName());
        }
    }
}

Output:

first note
stream stopped: NoSuchFileException
  • readUnchecked has no throws clause, so it can be used as a Function<Path, String> via the method reference Lambda2::readUnchecked.
  • new UncheckedIOException(e) keeps the original exception as its cause, so no information is lost. getCause() on UncheckedIOException is declared to return IOException, which is why no cast is needed.
  • The first file is printed; the second fails and the stream stops. The caller catches the unchecked wrapper and inspects the cause.

Wrapping and causes are explored further in the Exception Chaining lesson, and when to choose checked versus unchecked exceptions is covered in the next two lessons.

Deciding Between Catching and Declaring

SituationChoice
The method can fully recover (default value, retry, skip one record)Catch it
The method can't decide what the right response is; a caller canDeclare it with throws
The exception is a low-level detail that callers shouldn't depend on (e.g. a repository throwing SQLException)Catch it and throw an application-level exception with the original as the cause
Inside a lambda for the standard functional interfacesCatch it, or wrap it in an unchecked exception
The method is an entry point (request handler, scheduled job, main of a real app)Catch it, log it and report the failure

A good rule of thumb: handle an exception at the lowest level that has enough context to make a correct decision, and let it propagate with throws until it reaches that level.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Java throws Keyword and get answers drawn from it.

Signed-in readers only.