Annotation Interface ThrowsIf
@ThrowsIf(value = { b == 0 }, exception = ArithmeticException)
int divide(int a, int b) { a.intdiv(b) }
This is the exceptional counterpart of Requires: a precondition says
the caller must not do this (violating it is the caller's bug), whereas
a @ThrowsIf arm says this input is handled, by throwing — it is
defined behaviour, part of the contract, that callers may rely on (and
catch). The condition is a closure over the method's parameters, using the same
conventions as Requires.
Weaving. With the default woven = true the guard-throw is
generated at method entry — the general form of the pattern
groovy.transform.NullCheck provides for the null-check special case —
so the annotation is the implementation, not a comment about one:
if (b == 0) throw new ArithmeticException(...) // inserted; message derived from the conditionOn a constructor whose first statement is an explicit
super(...) or
this(...) call, the guard is inserted immediately after that
call, which must stay first — a language constraint.
With woven = false nothing is generated: the throw already exists.
Where it exists is the direct member — pure information for
readers and tools, with no effect on bytecode. direct = true (the
default): a hand-written throw statement lives in this body — checkable
documentation of a guard that is already there. direct = false: the
exception arises from code this method executes — a call, possibly
transitively (a third-party library), or a runtime operation — so there is no
throw statement in this body to find (and weaving a wrong specification about
invoked code would silently change behaviour, which is why such arms are
spelled woven = false). For woven code direct is implicitly
true and the member is ignored — most users never set it; a tool that cannot
find the promised throw in the body is the usual prompt to add
direct = false. The attributes are per-arm because real methods mix
modes:
@ThrowsIf(value = { x == null }, exception = NullPointerException) // woven for x@ThrowsIf(value = { y == null }, exception = NullPointerException, woven = false) // body guards y Object process(Object x, Object y) { ... }
Semantics. Read as an iff by default: the method throws a matching
exception exactly when some arm's condition holds — each arm is a
must-throw (condition on entry ⇒ the method throws, not returns),
and the arm-set as a whole claims only-when (a matching throw ⇒
some arm's condition held). exhaustive = false disclaims the only-when
half for the whole arm-set (a one-directional, JML signals-style arm):
the condition is sufficient for the throw but the set does not claim to
list every reason — useful when the full condition is inexpressible (for
example, Integer.parseInt: s == null is a true sufficient
condition, while "malformed or out of range" is not reasonably a parameter
closure). Note what neither mode claims: exhaustiveness is (at most) over the
conditions for the exception types mentioned, never over exception
types themselves — declaring an ArithmeticException arm says nothing
about whether the method can throw anything else. And no claim, however
exhaustive, reasons about VM resource conditions: an OutOfMemoryError
or StackOverflowError is outside contract semantics — the checked
wrapper passes any VirtualMachineError through unjudged, whatever the
arm types.
Checking. Weaving implements the contract; checked = true
additionally verifies it at runtime, in the same assertion style as
Ensures: on a normal return, no non-woven arm's condition may have held
(must-throw), and an escaping exception matching some arm's type must be
justified by a matching arm's condition having held on entry (only-when —
checked only for exhaustive arm-sets). A broken implementation raises
ThrowsIfViolation — never the declared
exception, which is defined behaviour delivered at entry; a justified throw
always propagates untouched. checked is set-level: if any arm
is checked, the whole arm-set is checked — every non-woven arm is
must-throw checked, and every arm serves as an only-when justifier (a sibling
cannot silently opt out). A pleasant corollary:
woven = false, direct = false plus checked = true
runtime-validates a claim about invoked third-party code — a wrong
specification is exposed, not silently believed.
The annotation is runtime-retained and repeatable, so the exceptional contract
is structured metadata available to documentation generators, static analysers,
verification tools, and AI coding agents — none of which can reliably consume
javadoc prose or discover guarded throw sites by body traversal. Only the
must-throw guard of a woven arm and the checked wrapper affect
generated code; exception, exhaustive and direct are
otherwise consumed by tools.
- Since:
- 6.0.0
- See Also:
-
Required Element Summary
Required Elements -
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionbooleantrue: verify the contract at runtime in the assertion style ofEnsures— a normal return with a non-woven arm's condition held, or an unjustified escaping throw of a matching type (exhaustivearm-sets only), raises aThrowsIfViolation.booleanInformation for readers and tools, with no effect on bytecode; ignored (implicitlytrue) for woven arms.The exception type thrown when the condition holds.booleanNo effect on generated code by itself.booleantrue(default): generate the guard-throw at method entry.
-
Element Details
-
value
Class valueReturns the closure class that evaluates the throw condition, a boolean expression over the method's parameters.- Returns:
- the generated closure class backing the condition
-
-
-
exception
The exception type thrown when the condition holds. A woven arm generatesnew <exception>(String), so the type should provide a(String)constructor (all the standard JDK exceptions do).- Returns:
- the exception type
- Default:
- java.lang.Throwable.class
-
woven
boolean woventrue(default): generate the guard-throw at method entry.false: the throw already exists (seedirectfor where) — nothing is generated.- Returns:
- whether the guard is generated
- Default:
- true
-
direct
boolean directInformation for readers and tools, with no effect on bytecode; ignored (implicitlytrue) for woven arms.true(default): a hand-writtenthrowstatement lives in this body.false: the exception arises from code this method executes — a call, possibly transitively, or a runtime operation — so there is no throw statement in this body to find. Most users never set this; a verification or analysis tool unable to find the promised throw in the body is the usual prompt.- Returns:
- whether the body itself contains the throw
- Default:
- true
-
exhaustive
boolean exhaustiveNo effect on generated code by itself. Underchecked = trueit gates the escaping-throw check:true(default) claims the listed conditions are the only reasons a matching exception is thrown, so an escaping match with no condition held is aThrowsIfViolation;falsesays the method may throw the same exception for other, unlisted reasons — escaping throws are never judged (and verification tools likewise skip the only-when direction). Onefalsearm disclaims the check for the whole arm-set. In specification terms:trueis an iff,falsea one-directional (JMLsignals-style) sufficient condition — the honest choice when the full condition is unstatable (Integer.parseInt:s == nullis a true sufficient half; "malformed or out of range" is not a parameter closure).- Returns:
- whether the arm-set claims to list every reason a matching exception is thrown
- Default:
- true
-
checked
boolean checkedtrue: verify the contract at runtime in the assertion style ofEnsures— a normal return with a non-woven arm's condition held, or an unjustified escaping throw of a matching type (exhaustivearm-sets only), raises aThrowsIfViolation.false(default): no runtime checking beyond anyWOVENguard.- Returns:
- whether the contract is runtime-checked
- Default:
- false
-