Skip to content

Fix Generator NPE on some Custom Scope Configurations - #1088

Merged
rbygrave merged 5 commits into
avaje:masterfrom
SentryMan:npe
Oct 14, 2026
Merged

rbygrave merged 5 commits into
avaje:masterfrom
SentryMan:npe

Conversation

@SentryMan

@SentryMan SentryMan commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator
  • When a require contains <, it becomes a constructor dependency written with its fully qualified type, with no import.
  • Generic custom scope dependencies emit builder.withBean(new GenericType<...>(){}.type(), genericVarName), so the bean is registered under its parameterized type.
  • currently generated modules are no longer treated like external
  • Also moduleData will now only split on commas outside <>, (), {} and []. Generics like Map<String, Long> and qualifiers like TempQualifier(a=0, b={...}) now stay whole when the csv is read back.

Resolves #1087 Resolves #1086

@rbygrave

Copy link
Copy Markdown
Contributor

I think we should move the static ExternalProvider.externalModules list into ProcessingContext.Ctx, so clear() resets it and the manual .clear() goes away. This avoids state carrying over between compiles in a long-lived JVM.

Does that make sense? What do you think?



Subject: [PATCH] No functional change - move externalModules into ProcessingContext.Ctx

Moves the static ExternalProvider.externalModules list into ProcessingContext.Ctx, so clear() resets it and the manual .clear() goes away. This avoids state carrying over between compiles in a long-lived JVM.
---
Index: inject-generator/src/main/java/io/avaje/inject/generator/ExternalProvider.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ExternalProvider.java b/inject-generator/src/main/java/io/avaje/inject/generator/ExternalProvider.java
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ExternalProvider.java	(revision 72d49dbd2123acc2c338c56512831a1c5b2134ae)
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ExternalProvider.java	(date 1792010277041)
@@ -62,7 +62,6 @@
     entry("io.avaje.validation.http.HttpValidatorProvider", of("io.avaje.http.api.Validator")));
   private static final String CLASS_NAME_AVAJE_MODULE = "io.avaje.inject.spi.AvajeModule";
   private static final List<MetaData> externalMeta = new ArrayList<>();
-  private static final List<ModuleData> externalModules = new ArrayList<>();
   private static final Map<String, List<MetaData>> externalModuleBeans = new LinkedHashMap<>();
 
   private ExternalProvider() {
@@ -303,7 +302,6 @@
     }
 
     final var avajeModuleType = typeElement.asType();
-    externalModules.clear();
     injectExtensions()
       .filter(t -> t.getInterfaces().stream().anyMatch(i -> types.isAssignable(i, avajeModuleType)))
       .distinct()
@@ -320,7 +318,7 @@
     try (final var moduleWriter = new FileWriter(APContext.getBuildResource("avaje-module-dependencies.csv").toFile())) {
       moduleWriter.write("External Module Type|Provides|Requires|SoftRequires");
       // only external modules, the modules generated by this compilation must not be read back as external
-      for (ModuleData avajeModule : externalModules) {
+      for (ModuleData avajeModule : ProcessingContext.externalModules()) {
         moduleWriter.write("\n");
         moduleWriter.write(avajeModule.name());
         moduleWriter.write("|");
@@ -369,8 +367,7 @@
     softRequires.removeAll(requires);
     APContext.logNote("Detected Module: %s", name);
     var moduleData = new ModuleData(name, List.copyOf(provides), List.copyOf(requires), List.copyOf(softRequires));
-    externalModules.add(moduleData);
-    ProcessingContext.addModule(moduleData);
+    ProcessingContext.addExternalModule(moduleData);
   }
 
   private static Stream<TypeElement> injectExtensions() {
Index: inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java b/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java	(revision 72d49dbd2123acc2c338c56512831a1c5b2134ae)
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java	(date 1792010277041)
@@ -53,6 +53,7 @@
     private final Set<String> optionalTypes = new LinkedHashSet<>();
     private final Map<String, AspectImportPrism> aspectImportPrisms = new HashMap<>();
     private final List<ModuleData> modules = new ArrayList<>();
+    private final List<ModuleData> externalModules = new ArrayList<>();
     private final List<TypeElement> delayQueue = new ArrayList<>();
     private final Set<String> spiServices = new TreeSet<>();
     private final Set<String> externalSpi = new TreeSet<>();
@@ -253,6 +254,15 @@
     CTX.get().modules.add(module);
   }
 
+  static void addExternalModule(ModuleData module) {
+    CTX.get().externalModules.add(module);
+    CTX.get().modules.add(module);
+  }
+
+  static List<ModuleData> externalModules() {
+    return CTX.get().externalModules;
+  }
+
   static List<ModuleData> modules() {
     return CTX.get().modules;
   }


@SentryMan

Copy link
Copy Markdown
Collaborator Author

This avoids state carrying over between compiles in a long-lived JVM.

The list is only read by the csv writer after a scan has emptied and refilled it. Leftovers from a previous compile in the same JVM (like a Gradle daemon) just sit in memory until cleared by the next scan. Even so either way is fine

@rbygrave rbygrave added this to the 12.8 milestone Oct 14, 2026
@rbygrave
rbygrave merged commit 33459e6 into avaje:master Oct 14, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants