JDK-8370800 : Downgrade cant.attach.type.annotations diagnostics to warnings
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 26
  • Priority: P3
  • Status: New
  • Resolution: Unresolved
  • Submitted: 2025-10-28
  • Updated: 2025-11-03
Related Reports
Relates :  
Description
As discussed in JDK-8346471, a compatibility impact of javac reading type annotations from the classpath is that it may load additional class files referenced by type annotations. This can cause compilation errors for compilations with incomplete classpaths, but which previously compiled because the missing classes were never loaded.

JDK-8337998 added handling for these completion failures, and introduced a new error diagnostic cant.attach.type.annotations.

There have been some reports of these cant.attach.type.annotations diagnostics where transitive dependencies were deliberately being excluded from classpaths, for example:

* https://github.com/spring-projects/spring-data-build/issues/2579
* https://github.com/spring-projects/spring-framework/commit/79795c8e092e74102d45a8455d4948aa77ee7608

These diagnostics have some similarities to other diagnostics for incomplete or inconsistent information in class files:

* unknown.enum.constant is a warning
* annotation.method.not.found is a lint 'classfile' warning

To reduce the compatibility impact of the cant.attach.type.annotations diagnostics, it may be worth considering downgrading them to warnings, or potentially xlint warnings (as part of 'classfile', or potentially a different xlint category).
Comments
To explore the handling between missing non-type-use and type-use annotations a bit, consider the following example: ``` $ javac *.java; javac -processor P T.java -sourcepath : ... Note: LA [@A] Note: @A DECLARED Note: f() [@TA] Note: @TA DECLARED ``` When using a class file that references missing annotation classes, missing type-use or non-type-use annotations does not result in an javac error, and are included in the model as ERROR types: ``` $ javac *.java; rm A.class TA.class; javac -processor P T.java -sourcepath : Note: LA [@A] Note: @A ERROR Note: f() [@TA] Note: @TA ERROR ``` Missing transitive classes that are referenced in the API result in a `cant.attach.type.annotations` diagnostic: ``` $ javac *.java; rm Lib.class; javac -processor P T.java -sourcepath : ./LTA.class: error: Cannot attach type annotations @TA to LTA.f: class file for Lib not found 1 error ``` One earlier javac versions without cant.attach.type.annotations, cant.access / class.file.not.found would have been reported if the code actually transitively referenced the missing class, e.g.: ``` void f(LTA lta) { lta.f(); } ... T.java:6: error: cannot access Lib lta.f(); ^ ``` so in practice here javac is more eagerly completing symbols that are referenced in the API of any libraries referenced by the current compilation, if those APIs have type annotations. === A.java === import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface A {} === LA.java === @A public class LA {} === Lib.java === class Lib<T> {} === LTA.java === class LTA { public Lib<@TA Object> f() { return null; } } === T.java === class T { LA la; LTA lta; } === TA.java === import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE_USE) @Retention(RetentionPolicy.RUNTIME) @interface TA {} === P.java import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; @SupportedAnnotationTypes("*") public class P extends AbstractProcessor { @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } boolean first = true; @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (!first) { return false; } Element t = processingEnv.getElementUtils().getTypeElement("LA"); processingEnv .getMessager() .printMessage(Diagnostic.Kind.NOTE, "%s [%s]".formatted(t, t.getAnnotationMirrors())); for (var anno : t.getAnnotationMirrors()) { processingEnv .getMessager() .printMessage( Diagnostic.Kind.NOTE, "%s %s".formatted(anno, anno.getAnnotationType().getKind())); } t = processingEnv.getElementUtils().getTypeElement("LTA"); ExecutableElement f = (ExecutableElement) t.getEnclosedElements().stream() .filter(e -> e.getSimpleName().contentEquals("f")) .findAny() .get(); TypeMirror ft = ((DeclaredType) f.getReturnType()).getTypeArguments().get(0); processingEnv .getMessager() .printMessage(Diagnostic.Kind.NOTE, "%s [%s]".formatted(f, ft.getAnnotationMirrors())); for (var anno : ft.getAnnotationMirrors()) { processingEnv .getMessager() .printMessage( Diagnostic.Kind.NOTE, "%s %s".formatted(anno, anno.getAnnotationType().getKind())); } first = false; return false; } } ===
31-10-2025

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk/pull/28018 Date: 2025-10-28 11:41:01 +0000
28-10-2025