throw vs throws
throw and throws look almost identical, but they serve completely different purposes. throw is an action — you use it to actually raise an exception. throws is a declaration — you use it in a method signature to warn callers that the method might raise certain exceptions. Mixing them up is one of the most common beginner stumbles in Java.
Quick Comparison
| Feature | throw | throws |
|---|---|---|
| What it is | A statement | A keyword in a method signature |
| Where it appears | Inside a method body | After the parameter list, before { |
| What it does | Raises an exception right now | Declares what exceptions might propagate |
| How many exceptions | Exactly one at a time | Can list multiple, comma-separated |
| Works with | Any Throwable instance | Class names only (no new, no instance) |
| Required for unchecked? | N/A (you just throw it) | No — optional for unchecked exceptions |
| Required for checked? | Use it inside the method | Yes — compiler enforces this |
throw — Raising an Exception
You use throw when something has gone wrong and you want execution to stop and unwind the call stack. You always throw an instance, created with new.
public class AgeValidator {
public static void validateAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
System.out.println("Age accepted: " + age);
}
public static void main(String[] args) {
validateAge(25); // prints normally
validateAge(-3); // throws immediately
}
}
Output:
Age accepted: 25
Exception in thread "main" java.lang.IllegalArgumentException: Invalid age: -3
at AgeValidator.validateAge(AgeValidator.java:4)
at AgeValidator.main(AgeValidator.java:10)
Notice that throw takes a fully constructed object (new IllegalArgumentException(...)), not just a class name. Execution halts at the throw line — any code below it in the method is unreachable.
Note:
throwcan appear anywhere inside a method body, inside a constructor, or even inside a static initializer block. There is no restriction on where it lives, as long as it appears in executable code.
throws — Declaring Propagated Exceptions
throws belongs in the method signature. It tells the compiler — and your fellow developers — “this method may not handle certain checked exceptions itself; the caller must deal with them.”
import java.io.FileReader;
import java.io.IOException;
public class FileProcessor {
// declares that it might throw IOException
public static void openFile(String path) throws IOException {
FileReader reader = new FileReader(path);
System.out.println("File opened: " + path);
reader.close();
}
public static void main(String[] args) {
try {
openFile("data.txt");
} catch (IOException e) {
System.out.println("Could not open file: " + e.getMessage());
}
}
}
throws IOException on openFile tells the compiler two things:
openFilemay propagate anIOExceptionto its caller.- Callers must either
catchit or declare their ownthrows IOException.
Without that throws declaration, the compiler refuses to compile — FileReader constructor throws a checked IOException and Java insists you handle it somewhere.
Tip:
throwscan list multiple exception types separated by commas:throws IOException, SQLException, ParseException. List only exceptions a caller genuinely needs to know about — don’t dump every possible exception into the signature.
Side-by-Side Example
This single class shows both keywords working together. The inner method uses throw to raise the exception; the outer method uses throws to pass responsibility to its caller.
import java.io.IOException;
public class ThrowVsThrowsDemo {
// throws declares the checked exception — the caller must handle it
public static void readConfig(String filename) throws IOException {
if (filename == null || filename.isBlank()) {
// throw actually raises the exception with a message
throw new IOException("Filename must not be blank");
}
System.out.println("Reading config: " + filename);
}
public static void main(String[] args) {
// caller is forced to handle IOException because of 'throws'
try {
readConfig("app.properties"); // fine
readConfig(""); // throws IOException
} catch (IOException e) {
System.out.println("Config error: " + e.getMessage());
}
}
}
Output:
Reading config: app.properties
Config error: Filename must not be blank
The flow is: throws in the signature → compiler enforces handling → throw inside the body → JVM unwinds the stack at runtime.
Checked vs Unchecked: When throws Is Mandatory
This is where the rules differ most sharply.
Checked exceptions — throws is required
If you use throw with a checked exception (any Exception subclass that is not a RuntimeException), the compiler demands a matching throws declaration unless the exception is caught in the same method.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateParser {
// ParseException is checked — throws is mandatory here
public static Date parse(String dateStr) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(dateStr); // internally throws ParseException
}
}
Unchecked exceptions — throws is optional
You can freely throw unchecked exceptions (RuntimeException and its subclasses) without any throws declaration. Adding throws for unchecked exceptions is allowed but uncommon — it’s mainly used as self-documenting API design.
public class Calculator {
// throws ArithmeticException is optional here — just for documentation
public static int divide(int a, int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
return a / b;
}
}
Warning: Never declare
throws Errororthrows Throwablein a real API. Callers cannot meaningfully respond toErrorsubclasses (likeOutOfMemoryError), and declaringThrowableerases all useful type information for callers.
throws Does NOT Mean the Exception Is Always Thrown
A common misconception: a method with throws SomeException in its signature doesn’t have to throw that exception. It simply reserves the right to do so. The exception may or may not occur at runtime.
public static void maybeThrow(boolean fail) throws IOException {
if (fail) {
throw new IOException("Deliberate failure");
}
System.out.println("No exception this time.");
}
Both calls below are valid:
maybeThrow(false); // runs fine — no exception thrown
maybeThrow(true); // throws IOException
The caller must still be prepared to handle IOException even though it only fires on fail == true.
Inheriting and Overriding: throws Narrows, Never Widens
When you override a method, the overriding method’s throws clause can only be the same or narrower than the parent’s. It can never introduce new or broader checked exceptions.
import java.io.FileNotFoundException;
import java.io.IOException;
class Base {
public void read() throws IOException { }
}
class Child extends Base {
// OK — FileNotFoundException is a subtype of IOException (narrower)
@Override
public void read() throws FileNotFoundException { }
}
class Bad extends Base {
// COMPILE ERROR — Exception is broader than IOException
// @Override
// public void read() throws Exception { }
}
This rule ensures that code written against Base still compiles when it is given a Child at runtime — the Liskov Substitution Principle applied to exceptions.
Note: The narrowing rule applies only to checked exceptions. You can add, remove, or change unchecked exceptions freely in an override because the compiler doesn’t enforce them.
Under the Hood
How throw works in bytecode
The Java compiler translates throw someException; into a single bytecode instruction: athrow. This opcode pops the top reference from the operand stack (the exception object), validates that it’s a non-null Throwable, and triggers the JVM’s exception dispatch mechanism — searching the current method’s exception table for a matching handler, then unwinding frames until one is found (executing finally blocks along the way).
The most expensive part of throwing is not athrow itself but the constructor call that precedes it. Throwable’s constructor calls fillInStackTrace(), a native method that snapshots the entire call stack into the exception object. This is why exceptions should not be used for ordinary flow control — constructing them is orders of magnitude slower than a simple if-branch.
How throws works at compile time
throws has zero runtime cost. It is purely a compile-time signal stored in the method’s descriptor inside the .class file (in the Exceptions attribute of the method_info structure). The JVM itself does not enforce throws at runtime — that enforcement is entirely the Java compiler’s job. This is why you can call Java methods from other JVM languages (like Kotlin or Scala) without worrying about checked exceptions; those languages simply ignore the Exceptions attribute.
Common Pitfalls
- Using
throwwithoutthrowsfor a checked exception — the compiler will stop you, but beginners sometimes try it. - Writing
throws new IOException()—throwstakes a class name, not an instance. Writingnewafterthrowsis a syntax error. - Declaring
throwsbut never throwing — valid but misleading. It forces every caller to add try-catch for no reason. Revisit whether the declaration is still needed. - Catching an exception in the same method that declares
throws— you can catch it locally AND still havethrowsin the signature (for other code paths), but if every code path catches it, thethrowsdeclaration is dead weight.
For a deeper look at each keyword on its own, see the throw keyword and throws keyword pages.
Related Topics
- throw Keyword — the statement that actually raises an exception at a specific point in your code
- throws Keyword — declare propagated checked exceptions in a method signature
- Exception Propagation — how uncaught exceptions travel up the call stack
- Custom Exceptions — create domain-specific exceptions to throw with meaning
- Method Overriding — how the
throwsnarrowing rule applies when you override methods - try-catch Block — catch the exceptions that
throwraises