Java throw and throws
Manually throw exceptions in Java with throw, and declare exceptions in method signatures with throws.
Java throw and throws
So far we've been catching exceptions other code threw. Now you'll see how to throw your own. Two keywords do most of the work — and they're easy to confuse because they're only one letter apart.
throw— a statement that raises an exception at runtime. One word, in code that runs.throws— a declaration in a method signature that says "this method may throw these exception types." It's checked by the compiler, never runs.
throw happens. throws warns. Keep that pair in mind.
Throwing an exception
throw takes an expression of type Throwable (or any subtype) and raises it. The current method exits immediately, the stack starts unwinding, and the exception begins its search for a matching catch.
if (amount < 0) {
throw new IllegalArgumentException("amount must be non-negative, got " + amount);
}Three details:
- You can only throw a
Throwable. The compiler enforces this —throw "oops";won't compile. - You always throw an instance, not a class.
throw new X(...), neverthrow X. - The instance can be one you constructed inline (common), or a pre-existing object (rare — exceptions carry stack traces from their construction, so reusing one freezes the wrong trace).
When to throw
Throw an exception when the current method can't fulfill its contract. Some clear cases:
- Invalid arguments —
IllegalArgumentExceptionfor "you called me wrong." - Wrong state —
IllegalStateExceptionfor "you called me at the wrong time" (e.g.next()on an empty iterator). - Missing data — domain-specific exceptions like
UserNotFoundException. - Failed external operations — IO errors, network errors. Usually these come from the call you just made, so you don't construct them yourself; you let them propagate, or wrap them in a higher-level exception.
The case not to throw: as a control-flow shortcut for normal results. "Throwing for control flow" is slow and confusing. If "not found" is a routine outcome, return an Optional<T>, not a NotFoundException.
Choosing a type
The built-in java.lang exceptions cover most cases without ceremony:
IllegalArgumentException— bad argumentIllegalStateException— wrong stateNullPointerException— a required argument was null (useObjects.requireNonNull)UnsupportedOperationException— operation not implemented (e.g. immutable list'sadd)ArithmeticException— math fault
When the failure is specific to your domain — "user not found," "invalid coupon," "config out of sync" — write a small custom class for it. Two chapters from now we'll do exactly that.
The throws clause
If your method might throw a checked exception that it doesn't catch itself, you must declare it:
public Config loadConfig(Path p) throws IOException, ParseException {
String text = Files.readString(p);
return parser.parse(text);
}The clause is part of the method contract. It tells every caller: "if you call me, you have to either catch these or declare them yourself." The compiler enforces this — that's what makes them checked.
A few rules:
- You only declare checked exceptions.
RuntimeExceptions and their subclasses are unchecked — declaring them is allowed but not required, and usually not done. - You can declare more types than you actually throw — useful when you're keeping the option open for future implementations, though it's mild noise.
- A method that overrides another can declare the same or fewer checked exceptions than the parent (and only subtypes of the declared ones). It can't add new ones. This is Liskov substitution applied to exceptions.
throw and throws together
A real method usually does both:
public User loadUser(String id) throws IOException {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("id must be non-blank");
}
String json = httpClient.get("/users/" + id); // may throw IOException
return parser.toUser(json);
}- The
throws IOExceptiondeclares the checked exception that can come fromhttpClient.get. - The
throw new IllegalArgumentException(...)raises an unchecked one for bad input. It doesn't need to appear in thethrowsclause.
Wrapping an exception
When a low-level exception isn't meaningful at your layer, wrap it in one that is. Pass the original as the cause so the trace stays intact:
try {
return Files.readString(configPath);
} catch (IOException e) {
throw new ConfigLoadException("could not load config from " + configPath, e);
}The second-argument constructor pattern — (message, cause) — is standard across Exception, IOException, and all the built-ins. When you write your own exception class, give it both constructors.
A worked example
A tiny banking-style helper that validates input with IllegalArgumentException, signals an empty account with IllegalStateException, and lets a checked exception bubble up to the caller via throws. The driver shows what each one looks like when thrown.
The three runtime cases — argument, state, and successful withdrawal — fall through one catch. The fourth, archive(), only compiles because main is allowed to catch Exception and because archive() declared throws ArchiveException. Try removing the throws clause and the program fails to compile.
What's next
The compiler treats some exceptions strictly (you must handle them) and others permissively (you don't have to). That split is the next chapter. Continue to Java checked vs. unchecked exceptions.
Practice
A method signature reads `public void save() throws IOException`. The method body is empty — it doesn't throw anything. Will it compile?