Individual setters can look fine on their own even when the combination they leave behind is not, so build() is where the whole object gets checked before it is allowed to exist. Here a lower bound greater than the upper bound is rejected only once build() runs.
class Range {
final int lo, hi;
private Range(int lo, int hi) { this.lo = lo; this.hi = hi; }
static class Builder {
private int lo, hi;
Builder lo(int v) { lo = v; return this; }
Builder hi(int v) { hi = v; return this; }
Range build() {
if (lo > hi) throw new IllegalStateException("lo must not exceed hi");
return new Range(lo, hi);
}
}
}
Range.Builder builder = new Range.Builder().lo(10).hi(5);
String outcome;
try {
builder.build();
outcome = "built";
} catch (IllegalStateException e) {
outcome = "rejected: " + e.getMessage();
}
System.out.println(outcome);
rejected: lo must not exceed hi
Run this yourself in the Online Java Compiler, spin up a live REST API in the API Sandbox, or practise with Java interview questions.
Published 2026-09-27