Assertions
The assert statement lets you state an invariant you believe must always hold — and have the JVM check it for you during development, at essentially zero cost in production.
Java's assert statement is a lightweight tool for stating internal assumptions — conditions the code assumes are always true because of how it's written, not because of anything an external caller supplied. Assertions are disabled by default at runtime, which makes them fundamentally different from exceptions used for input validation: they exist to catch programmer errors during development and testing, not to enforce contracts against untrusted input in production.
Basic Syntax
The assert statement has two forms: assert condition; and assert condition : message;. If assertions are enabled and condition evaluates to false, an AssertionError is thrown, carrying message (if given) as its detail message. If assertions are disabled (the default), the statement is skipped entirely — the condition expression is not even evaluated.
The assert line documents and (when enabled) actively enforces the assumption that b is non-zero. With assertions off, execution falls through to the division itself, which throws its own exception for a zero divisor.
public class AssertBasics {
static int divide(int a, int b) {
assert b != 0 : "divisor must not be zero, caller violated precondition";
return a / b;
}
public static void main(String[] args) {
System.out.println(divide(10, 2));
// With assertions enabled and divide(10, 0): AssertionError before the division even runs
// With assertions disabled (default): the assert line is skipped, and a / b throws ArithmeticException instead
}
}Enabling Assertions
Assertions are disabled by default when running any Java program (java SomeClass runs with them off, silently skipping every assert statement). They must be explicitly enabled with the -ea (or -enableassertions) flag on the java command.
This default-off behavior exists for backward compatibility (assert was added in Java 1.4 as a keyword, and pre-existing code that used "assert" as an identifier would have broken if assertions were on by default) and for performance: production code typically runs with assertions off, so assertion checks cost nothing at runtime unless a developer deliberately turns them on -- most commonly during testing and development.
java -ea SomeClass # enable assertions in all non-system classes
java -ea:com.example... SomeClass # enable only for a specific package/class
java -da:com.example.Legacy SomeClass # disable for one specific class while -ea is on
java -esa SomeClass # enable assertions in system (bootstrap) classes tooAppropriate Use: Internal Invariants
Assertions are for conditions that should be impossible given correct code — not conditions that depend on external, untrusted input. A good candidate is a switch statement's default branch that the code believes is unreachable given how an enum was defined, or a postcondition that a private helper method's own logic guarantees, or an invariant only your own class's internal code could violate.
Throwing AssertionError directly (a common alternative to assert in an unreachable branch) documents that reaching this code indicates a programming error -- most likely a new enum constant added without updating this switch -- not a runtime data problem.
enum Direction { NORTH, SOUTH, EAST, WEST }
public class InvariantCheck {
static String opposite(Direction d) {
switch (d) {
case NORTH: return "SOUTH";
case SOUTH: return "NORTH";
case EAST: return "WEST";
case WEST: return "EAST";
default:
// Given Direction's four constants, this branch is unreachable --
// unless someone adds a fifth constant and forgets to update this method.
throw new AssertionError("unreachable: unknown Direction " + d);
}
}
}Assertions Versus Input Validation
This is the single most important distinction to internalize: assertions must never be used to validate arguments on a public API, because assertions are disabled by default in production. If a public method relies on assert to reject a bad argument, that check silently vanishes the moment the program runs without -ea — which is the normal, default way Java programs run.
withdrawBad silently allows overdrafts in any normal (assertions-off) production run, because the assert is simply skipped. withdrawGood always enforces the rule, regardless of how the JVM was launched.
Rule of thumb: use exceptions (IllegalArgumentException, IllegalStateException, a custom exception) for anything a public API's caller could get wrong. Use assert only for conditions your own code's internal logic guarantees, where a violation indicates a bug in that code itself, not a caller mistake.
public class ValidationVsAssertion {
// WRONG: this "validation" does nothing when assertions are disabled (the default)
static void withdrawBad(double balance, double amount) {
assert amount <= balance : "insufficient funds";
// proceeds with the withdrawal even when amount > balance, in production
}
// RIGHT: input validation must always run, regardless of assertion status
static void withdrawGood(double balance, double amount) {
if (amount > balance) {
throw new IllegalStateException("insufficient funds");
}
}
}Assertions Versus Exceptions: A Comparison
Both assertions and exceptions can signal "something is wrong," but they serve different audiences and different lifecycle stages.
AssertionError extends Error, not RuntimeException — signaling "this is not something the program is expected to catch and recover from," consistent with the idea that a failed assertion represents a bug, not a handled condition.
assert Exception (e.g. IllegalArgumentException)
Enabled by default? No (needs -ea) Yes, always
Who can violate it? Only your own code's bugs Any caller, including untrusted external input
Intended audience Developers, during dev/testing All callers of the API, in production
Cost when off Zero (skipped entirely) Always evaluated
Recoverable? No -- AssertionError is an Error Often yes -- callers can catch and handle itPreconditions, Postconditions, and Invariants
Assertions are commonly grouped by what they check: preconditions (a state that should be true before a piece of internal logic runs, established by earlier code you control — not external callers), postconditions (a state a method's own logic guarantees is true right before it returns), and class invariants (a condition that should hold for an object between any two public method calls).
isSorted() is an internal check on a postcondition the insertion logic itself is supposed to guarantee. If a future refactor of insertSorted's shifting logic introduces a bug, this assertion (when enabled, e.g. during test runs) catches it immediately at the point of the bug, rather than as a mysterious ordering failure discovered much later.
public class SortedIntBuffer {
private final int[] data;
private int size;
SortedIntBuffer(int capacity) { data = new int[capacity]; }
void insertSorted(int value) {
// ... internal insertion logic that shifts elements to keep 'data' sorted ...
size++;
assert isSorted() : "postcondition violated: buffer not sorted after insert";
}
private boolean isSorted() {
for (int i = 1; i < size; i++) {
if (data[i - 1] > data[i]) return false;
}
return true;
}
}Practical Usage in Testing and CI
Because assertions are off by default, a common and valuable practice is to run test suites and CI pipelines with -ea enabled, so any internal-invariant assertion you've written gets a chance to actually fire during automated testing, catching bugs before code reaches production — while production itself typically still runs with assertions off for performance and to avoid an AssertionError crashing a live process over a condition designed only to alert developers.
Some teams do choose to run assertions in production for defense-in-depth on critical invariants — this is a deliberate operational trade-off (a small performance cost, and an AssertionError instead of graceful degradation) rather than the default expectation.
Edge Cases
Because a disabled assertion's condition expression is never evaluated, the condition must not have side effects the rest of the program depends on — assert list.remove(item); is a classic bug, since with assertions off, the item is never removed at all, silently changing program behavior depending on whether -ea was passed.
Assertions can be selectively enabled/disabled per package or class (-ea:com.example..., -da:com.example.SomeClass), which is occasionally useful for isolating which subsystem's assertions to check, but adds operational complexity most projects don't need — a blanket -ea (all) or nothing is the common case.
Best Practices
Always include a descriptive message (assert cond : "...") explaining what invariant was violated and why it matters — a bare AssertionError with no message is much harder to diagnose. Never rely on assertion side effects. Never use assert to validate arguments on a public, externally-callable API. Do use assertions liberally for internal invariants, postconditions, and "this should be impossible" branches, and enable -ea when running your test suite.
Quick Reference
assert condition; // throws AssertionError if false and enabled
assert condition : message; // AssertionError carries message
Default at runtime: DISABLED // must pass -ea to enable
java -ea MyClass // enable for all (non-system) classes
java -ea:com.example... MyClass // enable for one package
AssertionError extends Error // not meant to be caught/handled by callers
Never use assert for: argument validation on a public API
Do use assert for: internal invariants, postconditions, "impossible" branchesKey Takeaways
assert condition [: message] throws AssertionError when the condition is false, but only if assertions are enabled -- which they are NOT by default; -ea must be passed explicitly.
Never use assert to validate arguments on a public API -- because assertions are off by default in production, such a check silently does nothing under normal execution.
Use assert for internal invariants, preconditions/postconditions your own code guarantees, and branches that should be logically unreachable given correct code.
AssertionError extends Error, not RuntimeException, signaling that a failed assertion represents a bug to fix, not a condition callers are expected to catch and handle.
Never write an assert condition with side effects the program depends on -- when assertions are disabled, the condition expression is never evaluated at all.
Run your test suite with -ea so internal assertions actually get exercised during development, even if production itself runs with assertions off.
public void setAge(int age) { assert age >= 0: "age cannot be negative"; this.age = age; } public void setAge(int age) { if (age < 0) { throw new IllegalArgumentException("age cannot be negative: " + age); } this.age = age; }
A public setter's argument comes from a caller Java cannot control; using assert here means the check is silently skipped whenever the program runs without -ea, which is the default -- exceptions must be used for public-API validation.
assert queue.poll() != null : "queue should not be empty here";
Object item = queue.poll(); assert item != null : "queue should not be empty here";
poll() has a side effect (removing an element); if assertions are disabled, the condition expression is never evaluated, so the element is never removed, silently changing program behavior depending on the -ea flag.
throw new AssertionError("should never happen"); // no context at all throw new AssertionError("unreachable: unexpected value " + value + " in switch on " + variableName);
A bare, unexplained AssertionError message gives a future debugger (possibly you, months later) almost nothing to work with; including the actual offending value and context makes the failure immediately diagnosable.
Why are Java assertions disabled by default, and what flag enables them?
Primarily for backward compatibility (assert was added as a keyword in Java 1.4, and existing code using "assert" as an identifier would break if it were reserved and active by default) and for zero runtime cost in production; -ea (or -enableassertions) on the java command enables them.
Explain, with a concrete example, why using assert to validate a public method's arguments is a bug waiting to happen.
Because assertions are off by default, a public method like 'assert amount > 0; withdraw(amount);' will silently skip that check in any normal production run (no -ea flag), allowing invalid arguments (e.g. a negative or zero amount) straight through with no validation at all -- exactly the arguments the check was meant to catch.
What class does AssertionError extend, and why does that matter for how callers are expected to treat a failed assertion?
It extends Error, not RuntimeException, signaling that a failed assertion represents an unrecoverable programming bug that callers are not expected to catch and handle -- unlike exceptions used for expected, recoverable failure conditions.
Why is 'assert list.remove(item);' considered dangerous even though it looks like normal validation syntax?
The condition expression of a disabled assertion is never evaluated at all; since remove(item) has a side effect (actually removing the element), disabling assertions means the removal silently never happens, making program behavior depend on whether -ea was passed -- a correctness bug hiding inside an assertion.
Give an example of a good use case for assert inside a private or internal method.
A postcondition a method's own logic is supposed to guarantee -- e.g. asserting a buffer is sorted immediately after an internal insertion routine runs -- or a switch's default branch that should be logically unreachable given a known, fixed set of enum constants, both of which indicate a bug in your own code if they ever fire, not a caller mistake.
How do assertions and exceptions differ in terms of who is expected to violate the condition being checked?
Assertions check conditions only your own code's internal logic could violate (a bug in your implementation); exceptions like IllegalArgumentException guard against conditions any external caller -- including untrusted or careless code you don't control -- could trigger by passing bad input or calling in the wrong state.
Why is it good practice to run a test suite with -ea even if production runs without it?
Running tests with assertions enabled gives internal invariant checks (postconditions, unreachable-branch checks) a chance to actually fire and catch bugs during development and CI, before the code ships; production then runs with assertions off for performance, since by that point the internal invariants have already been exercised extensively in testing.
Write a private helper method binarySearchInsertionPoint that returns where a value should be inserted into a sorted array, with an assertion checking (as a postcondition) that the returned index is within valid bounds.
public class InsertionPoint { static int binarySearchInsertionPoint(int[] sortedArr, int value) { int lo = 0, hi = sortedArr.length; while (lo < hi) { int mid = (lo + hi) >>> 1; if (sortedArr[mid] < value) lo = mid + 1; else hi = mid; } assert lo >= 0 && lo <= sortedArr.length : "postcondition violated: insertion index " + lo + " out of bounds for length " + sortedArr.length; return lo; } }
The assertion checks a postcondition the binary search logic itself is supposed to guarantee (the returned index is always a valid insertion point, 0..length inclusive); if a future edit to the loop's comparison logic introduces an off-by-one bug, this assertion (when run with -ea, e.g. in tests) fails immediately at the source of the bug rather than surfacing as a mysterious ArrayIndexOutOfBoundsException somewhere else later.
If this program is run with plain 'java Config' (no -ea flag), what does it print, and why?
It prints "Applying volume=500" -- the out-of-range value is applied with no error at all. Because assertions are disabled by default, the assert line is entirely skipped (its condition is never even evaluated), so the intended range check silently does nothing; this demonstrates exactly why assert must never be used for validating externally-supplied values on what is effectively a public entry point.
Write a method processOrder(OrderStatus status) with an enum switch where an unreachable default branch throws an AssertionError with a descriptive message, and explain when it would actually fire.
enum OrderStatus { PENDING, SHIPPED, DELIVERED, CANCELLED } public class OrderProcessor { static String processOrder(OrderStatus status) { switch (status) { case PENDING: return "Awaiting shipment"; case SHIPPED: return "On the way"; case DELIVERED: return "Complete"; case CANCELLED: return "Cancelled"; default: throw new AssertionError("unreachable: unhandled OrderStatus " + status); } } }
This branch is unreachable as long as OrderStatus has exactly these four constants and every one is handled above; it would only actually execute if a future developer adds a fifth enum constant (e.g. RETURNED) and forgets to add a corresponding case here -- at which point the thrown AssertionError immediately and clearly flags the missing case, rather than the method silently falling through with an unhandled scenario.
If this code runs with plain 'java' (no flags), what gets printed, and why does 'assert false' not stop execution?
"still running" is printed, and nothing about the AssertionError appears at all. Because assertions are disabled by default, 'assert false : "..."' is skipped entirely just like any other assert statement -- the false condition is never evaluated and no AssertionError is thrown, so execution simply falls through to the next line as if the assert statement weren't there.
public class Config {
static void applySetting(String key, int value) {
assert value >= 0 && value <= 100 : "value out of range";
System.out.println("Applying " + key + "=" + value);
}
public static void main(String[] args) {
applySetting("volume", 500); // called with an out-of-range value
}
}assert false : "reached forbidden path";
System.out.println("still running");