W3docs

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(...), never throw 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 argumentsIllegalArgumentException for "you called me wrong."
  • Wrong stateIllegalStateException for "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 argument
  • IllegalStateException — wrong state
  • NullPointerException — a required argument was null (use Objects.requireNonNull)
  • UnsupportedOperationException — operation not implemented (e.g. immutable list's add)
  • 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 IOException declares the checked exception that can come from httpClient.get.
  • The throw new IllegalArgumentException(...) raises an unchecked one for bad input. It doesn't need to appear in the throws clause.

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.

java— editable, runs on the server

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

Practice

A method signature reads `public void save() throws IOException`. The method body is empty — it doesn't throw anything. Will it compile?