The Java SDK needs a compile-time gate that prevents accidental use of experimental APIs (types and methods marked with @CopilotExperimental). The annotation processor must detect consumer-side references to experimental elements and emit a compilation error unless the consumer explicitly opts in with -Acopilot.experimental.allowed=true.
The fundamental question is: should the processor use the Compiler Tree API (com.sun.source.util.Trees, TreePathScanner) for full expression-level coverage, or restrict itself to standard JSR 269 (javax.lang.model.*) for portability at the cost of reduced detection scope?
Uses Trees.instance(processingEnv) and TreePathScanner to walk the full AST of every compilation unit, resolving symbols at expression level.
What it catches additionally:
new ExperimentalType()inside method bodiesExperimentalType.staticMethod()inline calls- Method references (
ExperimentalType::method) - Local variable types
- Casts to experimental types
Drawbacks:
- Depends on
jdk.compilermodule — ties the processor to javac specifically. - Does not work with ECJ (Eclipse Compiler for Java), which has its own AST.
- Requires
requires static jdk.compilerin module-info.java. - Requires
--add-modules jdk.compiler --add-exports jdk.compiler/com.sun.source.util=ALL-UNNAMED --add-exports jdk.compiler/com.sun.source.tree=ALL-UNNAMEDin surefire test configuration. - The
com.sun.source.*package, while more stable thancom.sun.tools.javac.*, is still not part of the Java SE specification. It is a JDK-specific API.
Uses only standard annotation processing APIs to walk declared elements (types, methods, fields) and inspect their type mirrors.
What it catches:
- Field types referencing experimental classes
- Method parameter types
- Method return types
- Superclass / implemented interfaces
- Thrown exception types
- Generic type arguments and bounds
What it cannot catch:
new ExperimentalType()purely inside a method body with no declaration footprint- Inline static method calls with no stored result
- Method references to experimental methods
- Local variable types (not visible to processors)
Advantages:
- Works with any compliant Java compiler (javac, ECJ, IntelliJ's compiler, etc.)
- No dependency on JDK-internal modules
- No
--add-exportshacks in build configuration - Simpler module-info (no
requires static jdk.compiler) - Easier to maintain and less fragile across JDK versions
Chosen: Option 2 — Pure JSR 269.
-
The SDK's experimental APIs are predominantly types (records, classes). Table
apiNotefrom the codegen analysis shows 316 experimental types vs. 159 experimental methods. Any meaningful use of an experimental record (params, results, events) requires declaring it somewhere — a field, a method parameter, a return type, or a superclass. Pure body-level usage with zero declaration footprint is a degenerate edge case for this SDK. -
Portability matters for a published library. The SDK is distributed on Maven Central. Consumers may use Eclipse (ECJ), IntelliJ's compiler, or other toolchains where
com.sun.source.*is unavailable. A processor that silently does nothing on non-javac compilers provides false confidence. -
Build simplicity. Avoiding
jdk.compilereliminates module-system friction: norequires static jdk.compiler, no--add-exportsin surefire, no risk ofIllegalAccessErroron future JDK versions that further restrict internal APIs. -
The gap is well-documented and acceptable. The README explicitly lists what the processor does and does not catch, with suggested workarounds. This transparency is preferable to a fragile implementation with full coverage.
-
Error Prone or similar tools can fill the gap later. If full expression-level enforcement becomes necessary in the future, it can be implemented as a separate Error Prone check (which is already designed for AST-level analysis) without changing the annotation or the processor's declaration-level behavior.
- Consumers who use experimental APIs only in fully-inline expressions (no field, no parameter, no return type) will not receive a compile error. This is expected and documented.
- The processor works identically across javac, ECJ, and any JSR 269-compliant compiler.
- No JDK-internal API dependency in the module descriptor or test infrastructure.
- Future enhancement path is clear: add an optional Error Prone check for body-level coverage without changing the existing processor.