The Cabe Standalone Processor
The Cabe Standalone Processor is a command-line tool that integrates Cabe bytecode instrumentation directly with your class files. 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
JSpecify annotations (typically org.jspecify:jspecify:1.0.0) in your compiled classes
The Cabe processor JAR file (cabe-processor-all.jar)
Downloading the Processor
You can download the Cabe processor JAR file from the Maven Central Repository:
Replace 3.3.0
with the version you want to use.
Command-Line Usage
The basic syntax for using the Cabe processor is:
Required Parameters
-i <input-folder>
: The directory containing the compiled class files to process-o <output-folder>
: The directory where processed class files will be written
Optional Parameters
-c <configuration>
: The configuration mode for Cabe processing (default: STANDARD)-cp <classpath>
: The classpath for resolving classes-v <verbosity>
: The verbosity level (0-3, default: 0)--help
: Display help information
Configuration Options
The Cabe processor supports several configuration modes that control how null checks are generated:
Predefined Configurations
STANDARD: Use standard assertions for private API methods, throw NullPointerException for public API methods (default)
DEVELOPMENT: Failed checks will always throw an AssertionError, also checks return values
NO_CHECKS: Do not add any null checks (class files are copied unchanged)
Custom Configurations
You can define a custom configuration using a configuration string with the following format:
Where <check>
can be one of:
NO_CHECK: No checks are generated
ASSERT: Standard Java assertions (controlled by -ea/-da JVM flags)
THROW_NPE: Throws NullPointerException
ASSERT_ALWAYS: Throws AssertionError regardless of JVM assertion settings
THROW_IAE: Throws IllegalArgumentException
You can also specify a single check to be used for all types:
For example:
THROW_NPE
- Use NullPointerException for all checkspublicApi=THROW_NPE:privateApi=ASSERT
- Use NullPointerException for public API and assertions for private API
Configuration 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 |
"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 |
Verbosity Levels
The -v
parameter controls the amount of information displayed during processing:
0: Show warnings and errors only (default)
1: Show basic processing information
2: Show detailed information
3: Show all information
Usage Examples
Basic Usage
Process class files from the build/classes
directory and write the processed files to the same directory:
Using a Different Configuration
Process class files using the DEVELOPMENT configuration:
Using a Custom Configuration
Process class files using a custom configuration:
Specifying a Classpath
Process class files with a specific classpath:
Increasing Verbosity
Process class files with increased verbosity:
Integration with Build Systems
While the standalone processor can be used directly, it's often more convenient to use the Cabe Maven or Gradle plugins for integration with build systems:
Troubleshooting
Common Issues
Class Files Not Being Processed
If your class files aren't being processed, check:
The input directory exists and contains class files
You have the correct permissions to read from the input directory and write to the output directory
The classpath includes all necessary dependencies
NoClassDefFoundError for JSpecify Annotations
If you get NoClassDefFoundError
for JSpecify annotations at runtime, make sure:
The JSpecify dependency is correctly included in your runtime classpath
The processed class files are being used at runtime
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.,
DEVELOPMENT
)Increase the verbosity level to get more detailed information about the processing
OutOfMemoryError
If you encounter an OutOfMemoryError, try increasing the Java heap size: