CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-apache-groovy--groovy-contracts

Design by contract support for Groovy with @Invariant, @Requires, and @Ensures annotations

Pending
Overview
Eval results
Files

annotations.mddocs/

Contract Annotations

Contract annotations define design-by-contract specifications using closure expressions. All annotations are in the groovy.contracts package and work with AST transformations to inject runtime assertion checks.

Important: All contract annotations (except @Contracted) are marked with @Incubating, indicating they are subject to breaking changes in future versions until the API is stabilized.

Package Enablement

@Contracted

@Target([ElementType.PACKAGE, ElementType.TYPE])
@Retention(RetentionPolicy.RUNTIME)
@interface Contracted

Enables contract processing for packages or classes. Must be applied before using other contract annotations.

Usage Example:

@Contracted
package com.example.contracts

import groovy.contracts.*

Class Invariants

@Invariant

@Target([ElementType.TYPE])
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Invariants.class)
@Incubating
@interface Invariant {
    Class value()
}

Defines class invariants that must hold throughout the object's lifetime. Invariants are checked:

  • After constructor execution
  • Before method calls
  • After method calls

Parameters:

  • value: Closure class containing the invariant condition

Contract Rules:

  • Can only reference class fields
  • Combined with parent class invariants using logical AND
  • Must return boolean or truthy value

Usage Examples:

@Invariant({ balance >= 0 })
class BankAccount {
    private BigDecimal balance = BigDecimal.ZERO
    
    void withdraw(BigDecimal amount) {
        balance -= amount
    }
}

Multiple Invariants:

@Invariant({ elements != null })
@Invariant({ elements.size() >= 0 })
@Invariant({ capacity > 0 })
class Container {
    private List elements = []
    private int capacity = 10
}

Inheritance Example:

@Invariant({ name != null })
class Person {
    String name
}

@Invariant({ employeeId != null })
class Employee extends Person {
    String employeeId
    // Combined invariant: name != null && employeeId != null
}

Method Preconditions

@Requires

@Target([ElementType.CONSTRUCTOR, ElementType.METHOD])
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RequiresConditions.class)
@Incubating
@interface Requires {
    Class value()
}

Defines method preconditions that must be satisfied by callers. Executed as the first statement in method calls.

Parameters:

  • value: Closure class containing the precondition

Contract Rules:

  • Can reference method arguments and class fields
  • Combined with parent method preconditions using logical OR (weakening)
  • Must return boolean or truthy value

Usage Examples:

@Requires({ amount > 0 })
void deposit(BigDecimal amount) {
    balance += amount
}

@Requires({ divisor != 0 })
@Requires({ dividend != null })
BigDecimal divide(BigDecimal dividend, BigDecimal divisor) {
    return dividend.divide(divisor)
}

Multiple Arguments:

@Requires({ startIndex >= 0 && endIndex > startIndex && endIndex <= data.size() })
List getSubset(int startIndex, int endIndex) {
    return data[startIndex..<endIndex]
}

Inheritance Example:

class Shape {
    @Requires({ width > 0 })
    void setWidth(double width) {
        this.width = width
    }
}

class Rectangle extends Shape {
    @Requires({ width <= maxWidth })
    @Override
    void setWidth(double width) {
        super.setWidth(width)
    }
    // Combined precondition: width > 0 || width <= maxWidth
}

Method Postconditions

@Ensures

@Target([ElementType.CONSTRUCTOR, ElementType.METHOD])
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(EnsuresConditions.class)
@Incubating
@interface Ensures {
    Class value()
}

Defines method postconditions that guarantee behavior after method execution. Executed after method completion.

Parameters:

  • value: Closure class containing the postcondition

Contract Rules:

  • Can reference method arguments, class fields, result, and old values
  • Combined with parent method postconditions using logical AND (strengthening)
  • Must return boolean or truthy value

Special Variables:

  • result: Available for non-void methods, contains the return value
  • old: Map containing previous values of cloneable fields

Usage Examples:

Basic Postcondition:

@Ensures({ result != null })
String formatName(String firstName, String lastName) {
    return "${firstName} ${lastName}".trim()
}

Using Result Variable:

@Ensures({ result >= 0 })
@Ensures({ result == old.balance + amount })
BigDecimal deposit(BigDecimal amount) {
    balance += amount
    return balance
}

Using Old Values:

@Ensures({ balance == old.balance - amount })
@Ensures({ old.balance >= amount })
void withdraw(BigDecimal amount) {
    balance -= amount
}

Constructor Postconditions:

@Ensures({ name != null && name.length() > 0 })
@Ensures({ age >= 0 })
Person(String name, int age) {
    this.name = name
    this.age = age
}

Multiple Postconditions:

@Ensures({ result != null })
@Ensures({ result.size() <= originalList.size() })
@Ensures({ result.every { originalList.contains(it) } })
List<String> filterActive(List<String> originalList) {
    return originalList.findAll { isActive(it) }
}

Container Annotations

@Invariants

@Target([ElementType.TYPE])
@Retention(RetentionPolicy.RUNTIME)
@Incubating
@interface Invariants {
    Invariant[] value()
}

Container for multiple @Invariant annotations. Automatically used when multiple @Invariant annotations are applied.

@RequiresConditions

@Target([ElementType.CONSTRUCTOR, ElementType.METHOD])
@Retention(RetentionPolicy.RUNTIME)
@Incubating
@interface RequiresConditions {
    Requires[] value()
}

Container for multiple @Requires annotations. Automatically used when multiple @Requires annotations are applied.

@EnsuresConditions

@Target([ElementType.CONSTRUCTOR, ElementType.METHOD])
@Retention(RetentionPolicy.RUNTIME)
@Incubating
@interface EnsuresConditions {
    Ensures[] value()
}

Container for multiple @Ensures annotations. Automatically used when multiple @Ensures annotations are applied.

Best Practices

Contract Expression Guidelines

  1. Keep expressions simple: Complex logic should be in helper methods
  2. Avoid side effects: Contract expressions should not modify state
  3. Use meaningful variable names: Make contracts self-documenting
  4. Test contract violations: Ensure contracts catch intended violations

Performance Considerations

  1. Use JVM assertion flags: Control contract checking in production
  2. Avoid expensive operations: Keep contract evaluation lightweight
  3. Consider field access patterns: Minimize field copying for old values

Inheritance Patterns

  1. Document contract changes: Clearly specify how child contracts modify parent behavior
  2. Understand combination rules: Know how preconditions weaken and postconditions strengthen
  3. Test inheritance hierarchies: Verify contract behavior across class hierarchies

Install with Tessl CLI

npx tessl i tessl/maven-org-apache-groovy--groovy-contracts

docs

annotations.md

exceptions.md

index.md

tile.json