Repository: gwtproject/gwt (2025-08-20T16:43:18Z)

Issue:
HEAD-SNAPSHOT cannot resolve javax.annotation.processing.Generated annotation
Works with 2.12.2 but not with HEAD-SNAPSHOT

[gwt-demo-dagger.tar.gz](https://github.com/user-attachments/files/21546895/gwt-demo-dagger.tar.gz)

Instructions:
```shell
mvn gwt:codeserver -pl *-client -am
mvn jetty:run -pl *-server -am -Denv=dev
```
 Open http://localhost:8080/

You should see the following output in the terminal
```
[INFO] The code server is ready at http://127.0.0.1:9876/
[INFO] GET /recompile/app
[INFO]    Job com.github.natros.gwt.demo.App_1_0
[INFO]       starting job: com.github.natros.gwt.demo.App_1_0
[INFO]       binding: user.agent=safari
[INFO]       Compiling module com.github.natros.gwt.demo.App
[INFO]          Tracing compile failure path for type 'com.github.natros.gwt.demo.DaggerAppComponent'
[INFO]             [ERROR] Errors in 'file:/Users/fsousa/Projects/tmp/gwt-demo-dagger/gwt-demo-dagger/gwt-demo-dagger-client/target/generated-sources/annotations/com/github/natros/gwt/demo/DaggerAppComponent.java'
[INFO]                [ERROR] Line 4: The import javax.annotation.processing cannot be resolved
[INFO]                [ERROR] Line 7: Generated cannot be resolved to a type
[INFO]          [ERROR] Aborting compile due to errors in some input files
[INFO]       [ERROR] Compiler returned false
[INFO]       [WARN] recompile failed
[INFO]       [WARN] continuing to serve previous version
````




Discussion:
Most likely caused by https://github.com/gwtproject/gwt/pull/10132
Given that other annotations work, I don't think we _want_ to solve this by restoring a single annotation - the fact that it doesnt work seems to suggest that something is different about this annotation than the others...

I can reproduce locally with Java 21 - and the class seems to be on the classpath. But the JDT compiler is pretty sure that this doesn't exist - it isn't the same error message as #10020 either. I'm debugging into JDT to see what might have been different here.
@niloc132 could it be because this is in the `java.compiler` module whereas all the other removed annotations are either from `java.base` or their own .jars (e.g. JUnit)?
Possibly, still hunting - but I think it is unlikely since we don't respect/use java modules anywhere else.
More information:
`com.google.gwt.dev.javac.JdtCompiler#caseSensitivePathExists` is returning false for `javax/annotation/processing`, which signals to GWT's own classpath lookups that the package doesn't exist, and so the class cannot exist.

`getClassLoader().getResource("javax/annotation/processing/")` -> returns `null`
`getClassLoader().getResource("javax/annotation/")` -> returns `jar:file:/home/colin/.m2/repository/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar!/javax/annotation/`
`getClassLoader().getResource("javax/annotation/processing/Generated.class")` return `jrt:/java.compiler/javax/annotation/processing/Generated.class`

So the class exists for sure, but JDT believes that the package doesn't. We need a better way to look up packages - Classloader.getDefinedPackage could work here (getPackage itself is deprecated, but probably would be what we wanted, since we only want to test existence), but would need to be tested on each classloader from current upwards (or else risk that someone is running GWT in some way other than from its own main()).  Quick and dirty patch that just checks the parent classloader and seems to work:
```patch
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
index a0cea39efb..f19fe8d5a2 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -1094,7 +1094,8 @@ public class JdtCompiler {
   private static boolean caseSensitivePathExists(String resourcePath) {
     URL resourceURL = getClassLoader().getResource(resourcePath + '/');
     if (resourceURL == null) {
-      return false;
+      // Can't be found in jars or class directories - might be system defined, test the parent classloader
+      return getClassLoader().getParent().getDefinedPackage(resourcePath.replace('/', '.')) != null;
     }
 
     try {
```
EDIT: Sorry, the above doesn't work if the `Generated` class hasn't already been loaded. Not obviously seeing a way to force the package to be loaded.
Here's a possible way that may resolve this - it seems a little dirty, but it likewise seems documented, by https://bugs.openjdk.org/browse/JDK-8066492.

The `jrt:/` file system has two directories - `modules/` and `packages/`. While `modules/` lets you iterate the currently available modules, `packages/` lets you specify a Java package, and resolve which module that package belongs to. 

If we can't find a package as a directory on the classpath (in jars or in a classes dir), we can test if it exists, with roughly

```java
      String pkg = resourcePath.replace('/', '.');
      try (FileSystem jrt = FileSystems.newFileSystem(URI.create("jrt:/"), Map.of())) {
        return Files.exists(jrt.getPath("/packages", pkg));
      } catch (IOException e) {
        // deliberate fall-through, we couldn't find the package
      }
```



Patch:
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
index a0cea39efb..63c18fd4e1 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -78,8 +78,12 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -384,6 +388,8 @@ private static class INameEnvironmentImpl implements INameEnvironment {
 
     private final Map<String, CompiledClass> internalTypes;
 
+    private final FileSystem jrt = FileSystems.getFileSystem(URI.create("jrt:/"));
+
     public INameEnvironmentImpl(Set<String> packages, Map<String, CompiledClass> internalTypes) {
       this.packages = packages;
       this.internalTypes = internalTypes;
@@ -391,6 +397,11 @@ public INameEnvironmentImpl(Set<String> packages, Map<String, CompiledClass> int
 
     @Override
     public void cleanup() {
+      try {
+        jrt.close();
+      } catch (IOException e) {
+        // Ignore failure to close read-only jrt file system
+      }
     }
 
     @Override
@@ -547,8 +558,8 @@ private NameEnvironmentAnswer doFindTypeInClassPath(String internalName) {
 
           ClassFileReader classFileReader =
               ClassFileReader.read(openStream, resource.toExternalForm(), true);
-          // In case insensitive file systems we might have found a resource  whose name is different
-          // in case and should not be returned as an answer.
+          // In case-insensitive file systems we might have found a resource whose name is
+          // different in case and should not be returned as an answer.
           if (internalName.equals(CharOperation.charToString(classFileReader.getName()))) {
             return new NameEnvironmentAnswer(classFileReader, null);
           }
@@ -606,7 +617,7 @@ private boolean isPackage(String slashedPackageName) {
         return false;
       }
       // Include class loader check for binary-only annotations.
-      if (caseSensitivePathExists(slashedPackageName)) {
+      if (caseSensitivePackageExists(slashedPackageName)) {
         addPackages(packages, slashedPackageName);
         return true;
       } else {
@@ -614,6 +625,32 @@ private boolean isPackage(String slashedPackageName) {
         return false;
       }
     }
+
+    private boolean caseSensitivePackageExists(String slashedPackage) {
+      URL resourceURL = getClassLoader().getResource(slashedPackage + '/');
+      if (resourceURL == null) {
+        // Can't be found in jars or class directories - might be system defined, test jrt
+        String pkg = slashedPackage.replace('/', '.');
+        // This check is always case-sensitive - we're looking inside the modules, rather than on
+        // the host file system, so no additional checks are needed.
+        return Files.exists(jrt.getPath("/packages", pkg));
+      }
+
+      try {
+        File resourceFile = new File(resourceURL.toURI());
+        return Arrays.asList(resourceFile.getParentFile().list()).contains(resourceFile.getName());
+      } catch (URISyntaxException e) {
+        // The URL can not be converted to a URI.
+      } catch (IllegalArgumentException e) {
+        // A file instance can not be constructed from a URI.
+      }
+
+      // Some exception occurred while trying to make sure the name for the resource on disk is
+      // exactly the same as the one requested including case. If an exception is thrown in the
+      // process we assume that the URI does not refer to a resource in the filesystem and that the
+      // resource obtained from the classloader is the one requested.
+      return true;
+    }
   }
 
   /**
@@ -1091,28 +1128,6 @@ private static void addPackages(Set<String> packages, String slashedPackageName)
     }
   }
 
-  private static boolean caseSensitivePathExists(String resourcePath) {
-    URL resourceURL = getClassLoader().getResource(resourcePath + '/');
-    if (resourceURL == null) {
-      return false;
-    }
-
-    try {
-      File resourceFile = new File(resourceURL.toURI());
-      return Arrays.asList(resourceFile.getParentFile().list()).contains(resourceFile.getName());
-    } catch (URISyntaxException e) {
-      // The URL can not be converted to a URI.
-    } catch (IllegalArgumentException e) {
-      // A file instance can not be constructed from a URI.
-    }
-
-    // Some exception occurred while trying to make sure the name for the resource on disk is
-    // exactly the same as the one requested including case. If an exception is thrown in the
-    // process we assume that the URI does not refer to a resource in the filesystem and that the
-    // resource obtained from the classloader is the one requested.
-    return true;
-  }
-
   private static ClassLoader getClassLoader() {
     return Thread.currentThread().getContextClassLoader();
   }


Test patch:
diff --git a/dev/core/test/com/google/gwt/dev/javac/JdtCompilerTest.java b/dev/core/test/com/google/gwt/dev/javac/JdtCompilerTest.java
index 6ceeee6cc1..df120b7eee 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JdtCompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JdtCompilerTest.java
@@ -127,6 +127,19 @@ public void testSourceNames() throws Exception {
         "com.google.Outer.Inner", "com.google.Outer.Inner.Deep$est", "com.google.Pseudo$Inner")));
   }
 
+  public void testReferencesToJrtPackages() throws UnableToCompleteException {
+    // Pick two different annotations from different java modules, with no emulation in GWT,
+    // and confirm they can compile
+    assertUnitCompilesWithNoErrors("com.example.JrtPackageReference",
+        "package com.example;",
+        "import javax.annotation.processing.Generated;",
+        "import jdk.jfr.Label;",
+        "@Generated(\"foo\")",
+        "@Label(\"asdf\")",
+        "public class JrtPackageReference {",
+        "}");
+  }
+
   private void assertUnitCompilesWithNoErrors(String sourceName, String... sourceLines)
       throws UnableToCompleteException {
     CompilationUnit unit = compileUnit(sourceName, sourceLines);