Annotation Interface ThrowsIf


Represents a method's exceptional contract: the method throws the given exception when the given condition holds on entry.
 @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 condition
 
On 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
    Modifier and Type
    Required Element
    Description
    Returns the closure class that evaluates the throw condition, a boolean expression over the method's parameters.
  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
    boolean
    true: verify the contract at runtime in the assertion style of Ensures — a normal return with a non-woven arm's condition held, or an unjustified escaping throw of a matching type (exhaustive arm-sets only), raises a ThrowsIfViolation.
    boolean
    Information for readers and tools, with no effect on bytecode; ignored (implicitly true) for woven arms.
    Class<? extends Throwable>
    The exception type thrown when the condition holds.
    boolean
    No effect on generated code by itself.
    boolean
    true (default): generate the guard-throw at method entry.
  • Element Details

    • value

      Class value
      Returns 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

      Class<? extends Throwable> exception
      The exception type thrown when the condition holds. A woven arm generates new <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 woven
      true (default): generate the guard-throw at method entry. false: the throw already exists (see direct for where) — nothing is generated.
      Returns:
      whether the guard is generated
      Default:
      true
    • direct

      boolean direct
      Information for readers and tools, with no effect on bytecode; ignored (implicitly true) for woven arms. true (default): a hand-written throw statement 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 exhaustive
      No effect on generated code by itself. Under checked = true it 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 a ThrowsIfViolation; false says 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). One false arm disclaims the check for the whole arm-set. In specification terms: true is an iff, false a one-directional (JML signals-style) sufficient condition — the honest choice when the full condition is unstatable (Integer.parseInt: s == null is 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 checked
      true: verify the contract at runtime in the assertion style of Ensures — a normal return with a non-woven arm's condition held, or an unjustified escaping throw of a matching type (exhaustive arm-sets only), raises a ThrowsIfViolation. false (default): no runtime checking beyond any WOVEN guard.
      Returns:
      whether the contract is runtime-checked
      Default:
      false