The Cabe Gradle Plugin
The Cabe Gradle Plugin is a plugin that integrates Cabe bytecode instrumentation with Gradle projects. Cabe is a Java bytecode instrumentation tool that inserts runtime checks based on JSpecify annotations into your class files.
Purpose and Benefits
Cabe helps implement the Fail-Fast Principle by automatically adding runtime checks for annotated method parameters. This provides several benefits:
Early Detection: Violations of nullability contracts are detected immediately at the point of violation
Improved Debugging: Clear error messages that identify the exact parameter that violated the contract
Reduced Boilerplate: No need to manually write null checks for annotated parameters
Consistent Enforcement: Ensures that nullability contracts are enforced consistently across your codebase
Requirements
Java 17 or later
Gradle 8 or later
JSpecify annotations (typically org.jspecify:jspecify:1.0.0)
Usage
1. Add the Plugin to Your Build Script
Add the Cabe Gradle Plugin to your project's build.gradle.kts
file:
Or in a traditional Groovy build.gradle
file:
Alternatively, you can apply the plugin using the apply
method:
2. Add JSpecify Dependency
Add the JSpecify annotations to your project:
3. Configure the Plugin (Optional)
Configure the Cabe plugin using the cabe
extension:
Or using the configure
method:
4. Use JSpecify Annotations in Your Code
Use JSpecify annotations in your code to specify nullability contracts:
Configuration Options
The Cabe Gradle Plugin supports the following configuration options:
config
Controls the configuration mode for Cabe processing.
Possible values:
com.dua3.cabe.processor.Configuration.STANDARD: Use standard assertions for private API methods, throw NullPointerException for public API methods (default)
com.dua3.cabe.processor.Configuration.DEVELOPMENT: Failed checks will always throw an AssertionError, also checks return values
com.dua3.cabe.processor.Configuration.NO_CHECKS: Do not add any null checks (class files are copied unchanged)
Custom configuration: For advanced configuration (see Cabe documentation for details)
verbosity
Controls the level of logging output.
Possible values:
0: Show warnings and errors only (default)
1: Show basic processing information
2: Show detailed information
3: Show all information
Complete Example
Here's a complete example of a Gradle project using the Cabe Gradle Plugin:
Advanced configuration
Using different Configurations for Development and Release Builds
You can also automatically select a configuration based on your version string. In this example, strict checking is done for snapshot and beta versions whereas a release build will use the standard configuration:
Defining Custom Configurations
You can define a custom configuration that differs from the provided predefined configurations by providing a configuration String:
When using a configuration String, you can use either
a predefined name: "STANDARD", "DEVELOPMEN", "NOCHECKSS"
a single Check to be used public and private API and return values
multiple combination of keys ("publicApi", "privateApi", "returnValue") and checks; in this the remaining will be set to "NO_CHECK"
Examples:
Configuration String | Public API | Private API | Return Value |
---|---|---|---|
"STANDARD" | THROW_NPE | ASSERT | NO_CHECK |
"DEVELOPMENT" | ASSERT_ALWAYS | ASSERT_ALWAYS | ASSERT_ALWAYS |
"NO_CHECKS" | NO_CHECK | NO_CHECK | NO_CHECK |
"THROW_NPE" | THROW_NPE | THROW_NPE | THROW_NPE |
"ASSERT" | ASSERT | ASSERT | ASSERT |
"ASSERT_ALWAYS" | ASSERT_ALWAYS | ASSERT_ALWAYS | ASSERT_ALWAYS |
"NO_CHECK" | NO_CHECK | NO_CHECK | NO_CHECK |
"THROW_NPE" | THROW_NPE | THROW_NPE | THROW_NPE |
"publicApi=THROW_NPE" | THROW_NPE | NO_CHECK | NO_CHECK |
"publicApi=THROW_NPE:returnValue=ASSERT" | THROW_NPE | NO_CHECK | ASSERT |
"publicApi=THROW_IAE:privateApi=ASSERT" | THROW_IAE | ASSERT | NO_CHECK |
You can also use the standard record constructor of Configuration
Troubleshooting
Common Issues
Class Files Not Being Processed
If your class files aren't being processed, check:
The Java plugin is applied before the Cabe plugin
The Cabe plugin is correctly applied to your project
The build output directory is correctly set
NoClassDefFoundError for JSpecify Annotations
If you get NoClassDefFoundError
for JSpecify annotations at runtime, make sure:
The JSpecify dependency is correctly added to your project
The dependency scope is appropriate (usually
implementation
)For modular projects, your module requires the JSpecify module
Unexpected NullPointerExceptions
If you're getting unexpected NullPointerExceptions:
Check that your code respects the nullability contracts specified by the annotations
Consider using a different configuration during development (e.g.,
com.dua3.cabe.processor.Configuration.DEVELOPMENT
)Increase the verbosity level to get more detailed information about the processing
Enable assertions when running your application:
tasks.withType<JavaExec> { enableAssertions = true }