AlgoMaster Logo

throws Keyword

High Priority23 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

The throws keyword goes in a method signature and declares that the method can propagate one or more exceptions to its caller without catching them. The previous lesson covered throw, which is a statement inside a method body that raises an exception right now. throws is the other half of the picture: it announces, as part of the method's contract, that calling this method might result in one of these exception types being thrown. This lesson covers the syntax, the compiler's "handle or declare" rule for checked exceptions, how throws interacts with method overriding and interfaces, and when listing an unchecked exception is worth the noise.

What throws Declares and Why It Exists

A method's signature is part of its public contract. The return type tells callers what kind of value comes back. The parameter list tells them what to pass in. The throws clause tells them what can go wrong in a way they need to plan for. For checked exceptions, this isn't optional documentation; it's documentation the compiler enforces at every call site.

Here is a method that fetches an order from a repository. If the order doesn't exist, the method throws a checked exception so the caller can decide how to respond.

The throws OrderNotFoundException clause in the signature is doing three jobs at once. It tells human readers that this method can fail in this specific way. It tells the compiler to verify that every caller has a plan for that failure. And it lets the JVM unwind the stack with the right exception type when the failure actually happens at runtime.

OrderNotFoundException extends Exception, which makes it a checked exception. Checked exceptions are the ones the compiler tracks. The important property here is that the compiler will not let a caller ignore a checked exception that a method declares with throws.

Two more methods follow the same pattern, and they are reused throughout this lesson:

These three services (OrderRepository, PaymentService, WarehouseService) each declare one checked exception they can throw. They'll be the building blocks for the rest of the lesson.

Listing Multiple Exception Types

A method can throw more than one kind of exception, and the throws clause supports that with a comma-separated list. The order doesn't matter. Each type listed is independent of the others.

The shipOrder method delegates to three helpers. Each helper declares its own checked exception, and shipOrder doesn't catch any of them, so its own signature has to list all three. This is how exceptions travel upward through a call chain: each method either handles the exception locally or extends its own contract to include it.

There's no limit to the number of exception types in the list, but a signature with five or six different checked exceptions usually signals that the method is doing too much or that some of those exception types should share a common parent class. The mechanics are simple: write the types, separate them with commas, and the compiler treats each as part of the contract.

The "Handle or Declare" Rule

When a method calls another method that declares a checked exception in its throws clause, the calling method has exactly two legal options. It must either catch the exception with a try / catch block, or it must extend its own throws clause to include the same exception (or a supertype of it). There is no third option, and the compiler enforces this strictly.

The diagram shows the decision a caller faces when calling a method that declares a checked exception. The "do nothing" branch is a common first attempt, and the compiler refuses to let the code build.

Here's what each branch looks like in code. First, the "handle it" branch, where the caller catches the exception locally.

The handleCheckout method calls findById, which declares throws OrderNotFoundException. Because the call is inside a try block with a matching catch, handleCheckout is allowed to omit throws OrderNotFoundException from its own signature. The exception is dealt with locally, so it never escapes the method.

Now the "pass it up" branch:

The handleCheckout method here doesn't catch the exception. Instead, it adds throws OrderNotFoundException to its own signature, which tells its callers (including main) that they now have the same handle-or-declare decision to make. main chooses to propagate further by declaring it as well, which is legal for short programs.

Now the case the compiler rejects. The method does neither:

What's wrong with this code?

The compiler emits:

The fix is to pick a branch: either wrap the call in a try / catch, or add throws OrderNotFoundException to handleCheckout. Without one or the other, the code does not compile. This is the central enforcement that makes a checked exception "checked": the compiler refuses to let a caller silently ignore it.

When a method calls several other methods, each one is judged independently. Some exceptions can be caught while others are declared.

This method handles OrderNotFoundException locally with a catch block but lets PaymentDeclinedException propagate up by declaring it. Both choices are legal because each checked exception gets its own handle-or-declare decision.

Unchecked Exceptions and throws

Unchecked exceptions (RuntimeException and its subclasses, plus Error and its subclasses) are not tracked by the compiler. A method can throw a NullPointerException or an IllegalArgumentException at any time, and no caller is required to catch or declare it. The relevant point is that unchecked exceptions do not need to be declared in a throws clause.

The applyDiscount method throws IllegalArgumentException when the discount percent is out of range. The signature has no throws clause. Callers don't need to wrap the call in try / catch. The compiler is happy because IllegalArgumentException extends RuntimeException, which puts it in the unchecked family.

An unchecked exception can be listed in a throws clause, but it's optional and rarely done. The most common reason is documentation: it signals to readers that this method intentionally throws this unchecked type, not as an accident.

The signature still compiles. Callers still don't need to handle the exception. The only effect is that the declaration shows up in Javadoc and in tooling, which can be useful when the unchecked exception is meaningful enough that readers should know about it. In practice, the more common style is to document unchecked exceptions in a Javadoc @throws tag rather than the throws clause, which keeps the signature clean while still informing readers.

A short table comparing how the two families interact with throws:

FamilyExamplesMust declare in throws?Caller must handle?
CheckedIOException, OrderNotFoundExceptionYes, if the method propagates itYes (catch or re-declare)
UncheckedNullPointerException, IllegalArgumentException, IllegalStateExceptionNo, optional for documentationNo, optional
ErrorOutOfMemoryError, StackOverflowErrorNoNo (and usually shouldn't)

The middle column drives behavior. Checked exceptions need declarations to travel; unchecked exceptions travel freely.

throws in Method Overriding

Once classes get involved, throws interacts with method overriding through a specific rule: an overriding method in a subclass may declare the same checked exceptions as the parent, fewer of them, or subtypes of them. It may not declare additional checked exceptions that the parent did not declare. The compiler enforces this for every override.

The rule exists because of the substitution principle. If Subclass extends Superclass, any code that holds a Superclass reference must be able to use a Subclass instance interchangeably. Callers wrote their handle-or-declare decision against the parent's signature. If the override could throw a new kind of checked exception, those callers would be missing a handler for it, and the compiler would have no way to know.

Here is the legal case. A subclass declares the same checked exception as the parent.

This compiles. The override declares exactly the same checked exception as the parent, so callers that already handle OrderNotFoundException for the parent are still safe.

Declaring fewer checked exceptions is also legal. The subclass is making a stronger promise than the parent (it can fail in fewer ways), which is fine for callers.

The parent declares two checked exceptions. The subclass declares only one. Callers of the parent expect to handle both, but they're free to handle either; encountering an instance that throws only the first is fine. The override is narrowing the contract, which is always safe.

A subclass override is also allowed to declare a subclass of the parent's exception. Here FetchFailureException is a parent type, and OrderNotFoundException extends it:

This is legal. Callers of OrderApi.findById already catch FetchFailureException, which covers OrderNotFoundException because the latter is a subtype. The override is allowed to narrow to the more specific subtype.

Now the case the compiler refuses.

What's wrong with this code?

The compiler emits:

The override added PaymentDeclinedException to the throws clause, and the parent didn't declare it. Any code that holds an OrderApi reference might actually be looking at a WideningOrderApi instance, so calling findById could throw PaymentDeclinedException at runtime. Callers wrote their code expecting only OrderNotFoundException, which means they have no handler for the new checked exception. The compiler stops the build.

Fix: either declare PaymentDeclinedException on the parent (which propagates the requirement to all callers), or catch it inside the override so it doesn't escape:

The override catches PaymentDeclinedException locally, so the exception never escapes the method. The throws clause only lists what the parent already declared. The compiler is satisfied because the override hasn't broken the parent's contract.

A useful framing: the parent's throws clause is an upper bound. The override can throw the same set, a subset, or replace any element with one of its subclasses, but it cannot add anything new.

throws on Constructors

Constructors can also declare checked exceptions in a throws clause, and they follow the same handle-or-declare rule that methods do. A constructor that loads a file, opens a network connection, or validates state in a way that can fail with a checked exception needs to tell callers what to plan for.

Calling new StoreConfig(...) is now a checkpoint where the caller must handle ConfigLoadException or declare it. The substitution rules from method overriding don't apply here because constructors aren't inherited in the same way, but the basic handle-or-declare rule still does.

throws on Interface Methods

Interface methods can declare throws clauses, and any class that implements the interface has to honor the same overriding rules. If the interface method declares throws CheckedX, an implementing method may declare the same, fewer, or subtypes of CheckedX, but it cannot add new checked exceptions.

Both implementations are legal. DatabaseOrderLookup declares exactly the same checked exception as the interface. FakeOrderLookup declares no checked exceptions at all, which is the "fewer" case from the overriding rules. Both are fine because the interface's throws clause is an upper bound: implementations can promise less but never more.

Trying to widen the interface's throws clause in an implementation produces the same kind of compile error as with class overrides. If OrderLookup.findById declares only OrderNotFoundException and an implementation adds PaymentDeclinedException, the compiler rejects the implementation as not legally overriding the interface method.

When a method is called through an interface reference, the compiler uses the interface's throws clause to decide what handle-or-declare obligations exist at the call site. This is why interfaces with thoughtful throws clauses are easier to evolve: implementations behind the interface can be swapped without callers needing to change their exception-handling code.

main(String[]) throws Exception

A program's main method is allowed to declare throws Exception (or any other checked exception). For short scripts and tests, this is a common shortcut: if any checked exception propagates all the way up, the JVM prints a stack trace and exits, which is exactly the behavior that fits a small demonstration program.

This compiles cleanly. The throws Exception in main covers every checked exception the method body might propagate, so no try / catch is needed. For a 10-line demo, that's an honest trade: it tells the reader "this demo isn't modeling error recovery, the JVM will print the trace if anything fails."

The trade-off is that throws Exception is too coarse for real programs. A signature that says throws Exception makes no distinction between a missing file and a corrupted database, so callers can't write specific handlers. In a production application, main usually catches what it expects, logs a meaningful message, and chooses an exit code. The "use throws Exception on main" shortcut is fine for tutorials, integration tests, and quick scripts. It's a code smell anywhere else.

For methods other than main, a throws Exception clause is almost always a sign of either a deliberate pass-through wrapper or sloppy design. It pushes the handle-or-declare burden upward without giving the caller any useful information about what specifically can fail. The best-practices lesson revisits this.

A Common Mistake: Silently Dropping a Declared Exception

A bug shows up when a method's throws clause is wider than it needs to be, or when a caller catches and ignores a checked exception that signals a real failure. The "swallowed exception" antipattern can hide problems for a long time.

What's wrong with this code?

The output is empty. The call findById(0) throws OrderNotFoundException. The empty catch block swallows the exception. No message prints, no error gets logged, and the calling code has no way to know that the lookup failed. The program runs as if nothing happened.

The compiler is satisfied because the catch block is technically legal: it caught the exception, fulfilling the handle-or-declare rule. The catch does nothing with the exception, which converts a meaningful failure signal into nothing at all.

Fix: either handle the exception in a way that's visible to operators (log it, return a sentinel, change the program flow), or let it propagate by removing the try / catch and adding throws OrderNotFoundException to the signature.

The catch block now prints the exception's message, so a real failure produces visible output. The handle-or-declare rule was meant to make checked exceptions impossible to ignore. Empty catch blocks defeat that purpose, and they deserve suspicion. There are legitimate uses, but in most code an empty catch block is a bug waiting to be discovered.

When to Declare an Exception vs. Catch It

The decision between catching a checked exception and declaring it on the current method's throws clause comes down to a simple question: does the current method have enough information to do something meaningful about the failure?

Catch the exception locally when the answer is yes. The method has a reasonable fallback (use a cached value, return a default, prompt the user), or it can recover internally without needing to involve callers. Catching here keeps the failure detail close to the code that knows how to react.

Declare the exception in throws when the answer is no. The method doesn't have the context to recover, but its caller might. Maybe the caller can retry, switch strategies, or surface a user-facing error. Pushing the exception up the call stack moves the decision to a method that can act on it.

The tryCheckout method is the right place to catch all three checked exceptions because it has the context: this is the top-level operation the user is initiating, and the right response to any failure is "report the problem and return false." If tryCheckout instead declared all three exceptions in its throws clause, every caller would have to repeat this same handler, which spreads the failure-handling logic across the code instead of centralizing it.

The opposite mistake is catching too early. A low-level helper that catches IOException and swallows it (or wraps it in something less specific) takes away the caller's ability to make an informed decision. This is why library code tends to declare exceptions and let application code catch them: the library doesn't know how the application wants to handle failures, but the application does.

Quiz

throws Keyword Quiz

10 quizzes