What are the idiomatic Java techniques for fail-fast input validation in methods and constructors, and which exception should each case use?
answer
- Validate at the top, before any side effects
- Objects.requireNonNull → NPE for nulls
- Bad argument → IAE; bad object state → ISE; bounds → IOOBE
- Constructor + final fields = always-valid object
- All unchecked (programmer errors); document with @throws
basics
~20 sCheck arguments at the very top of a method or constructor before doing work: use Objects.requireNonNull for nulls, throw IllegalArgumentException for bad values, and IllegalStateException when the object isn't in a valid state for the call. This stops bad data immediately.
solid answer
~40 sIdiomatic fail-fast validation happens at the **start** of public methods and constructors, before any side effects. For nulls, use `Objects.requireNonNull(x, "message")`, which throws NPE with a clear message. For an out-of-range or otherwise invalid **argument**, throw `IllegalArgumentException`; if the **object itself** is in the wrong state for the call, throw `IllegalStateException`. There are convenience helpers: `Objects.requireNonNullElse`, and `Objects.checkIndex`/`checkFromToIndex` for bounds (throwing IndexOutOfBoundsException). Validating in the **constructor** with `final` fields is the strongest form — the object can never exist in an invalid state, so later methods need no re-checks. For deeply nested or cross-field rules, a domain-specific validation framework (e.g. Bean Validation/`@NotNull`,`@Min`) can complement code-level checks. The guiding contract: bad argument → IllegalArgumentException, bad state → IllegalStateException, null → NPE, and document these in the method's Javadoc `@throws`.
code
java · 22 linespublic final class Order {
private final String id;
private final int quantity;
private boolean submitted;
public Order(String id, int quantity) {
// fail-fast: validate BEFORE assigning final fields
this.id = Objects.requireNonNull(id, "id must not be null");
if (quantity <= 0) {
throw new IllegalArgumentException("quantity must be positive: " + quantity);
}
this.quantity = quantity;
}
/** @throws IllegalStateException if already submitted */
public void submit() {
if (submitted) {
throw new IllegalStateException("order already submitted: " + id);
}
submitted = true;
}
}go deeper
Can add null checks and simple range checks at the start of a method and throw an exception, using Objects.requireNonNull for nulls.
Chooses the right exception per case (IAE/ISE/NPE/IndexOutOfBounds), validates in constructors, and knows the standard Objects helpers.
Designs always-valid objects via constructor validation + final fields, documents preconditions with @throws, pairs validation with defensive copying, and keeps contract violations unchecked.
Sets validation conventions across boundaries (trusted vs untrusted), decides when to use Bean Validation vs hand-rolled invariants, and ensures API contracts and error semantics are consistent system-wide.
## Goal Fail-fast validation = check that inputs and object state are valid **before** the method does anything, and throw immediately if not. This section is the *how-to* and the *which-exception* of the fail-fast principle. ## Where to validate 1. **Top of public methods**, before any side effects, so a rejected call leaves the world unchanged. 2. **Constructors**, so an invalid object can never be created. Combined with `final` fields, this gives a permanently-valid object (the invariant holds for life), and downstream methods can skip re-validation. 3. **Boundaries** (controllers, deserialization edges) — validate untrusted external input here; internal trusted code can rely on the invariants already established. ## The standard helpers and which exception to throw - **`Objects.requireNonNull(x)` / `requireNonNull(x, "msg")`** — throws `NullPointerException` if `x` is null. Idiomatic for null arguments; the message names the parameter. (Java treats a null argument as a special illegal argument, so NPE — not IAE — is the convention.) - **`IllegalArgumentException`** — the argument's *value* is unacceptable (negative size, empty string where non-empty required, value outside an allowed range). You typically `if (cond) throw new IllegalArgumentException("...")`. - **`IllegalStateException`** — the *object* is not in a state where the call makes sense (e.g. `start()` called twice, or using a closed resource). The problem is the receiver, not the argument. - **`Objects.checkIndex(index, length)`**, **`checkFromToIndex`**, **`checkFromIndexSize`** — bounds checks that throw `IndexOutOfBoundsException` with consistent messages. - **`Objects.requireNonNullElse(x, default)`** — supplies a default instead of throwing, for the cases where null legitimately means 'use default' (not a validation failure). ## Choosing the exception — the decision rule | Situation | Throw | |---|---| | Argument is `null` (and null not allowed) | `NullPointerException` (via `requireNonNull`) | | Argument value out of range / invalid | `IllegalArgumentException` | | Index/range out of bounds | `IndexOutOfBoundsException` (via `Objects.checkIndex`) | | Receiver object in wrong state for the call | `IllegalStateException` | All of these are **unchecked** (`RuntimeException` subclasses), because they signal **programmer errors** — the caller violated the contract. You don't force callers to catch them; you fix the calling code. ## Document the contract Declare the conditions in Javadoc with `@throws`: e.g. `@throws IllegalArgumentException if {@code count} is negative`. This makes the precondition part of the published API contract, which is the senior-level expectation. ## Constructor validation example pattern Validate, *then* assign to final fields. Never assign first and validate after — a thrown exception after partial assignment can leave references to a half-built object (especially dangerous if `this` escaped). ## Defensive copying For mutable inputs stored in a field (arrays, Date, collections), fail-fast pairs with **defensive copying**: validate, then copy, so a later external mutation can't break the invariant you just checked. Validate the copy if order matters. ## Beyond code-level checks For large objects with many cross-field rules, **Bean Validation** (`jakarta.validation`, annotations like `@NotNull`, `@Min`, `@Size`, custom validators) centralizes constraints and is enforced at framework boundaries (e.g. Spring controllers). It complements — does not replace — constructor invariants for core domain types. ## Anti-patterns to avoid - Validating *after* doing work or partial mutation (not fail-fast; can corrupt state). - Returning null/`-1`/empty instead of throwing on a contract violation (fail-silent). - Catching your own validation exception nearby (control-flow abuse). - Throwing a generic `RuntimeException` or `Exception` instead of the specific, conventional type.
- Why is NullPointerException, rather than IllegalArgumentException, the convention for a null argument?By long-standing Java convention (and Objects.requireNonNull), a null argument is treated as a special kind of illegal argument and maps to NPE. Using requireNonNull gives a clear, consistent message and is what readers and tools expect; reserve IAE for non-null but invalid values.
- Why validate before assigning to final fields in a constructor?So you never create a partially-initialized, invalid object. If you assigned first and a later check failed, a leaked or escaped 'this' reference (e.g. registered in a listener) could expose a half-built, invariant-violating object. Validate, then assign.
saying these in an interview costs you the question
- Throwing a generic Exception/RuntimeException instead of the specific conventional type (IAE/ISE/NPE/IOOBE).
- Validating after performing work or partial mutation, so a failure leaves corrupt state.
- Using checked exceptions for contract violations — these are programmer errors and should be unchecked.
- Forgetting defensive copies of mutable inputs, so the validated invariant can be broken later by external mutation.