Repository: avaje/avaje-inject (java)

Issue:
Replace Avaje Lang with Jspecify?
It seems [Jspecify has released `1.0.0`](https://github.com/jspecify/jspecify/releases/tag/v1.0.0), I'm thinking we should replace all of our custom nullable annotations with the ones from jspecify.

Pull request:
Support Jspecify Annotations
It seems due to their nature as `TYPE_USE` annotations we had not been detecting them correctly.

- replaces avaje lang with jspecify
- fixes generator to handle type use nullable annotations
- make jakarta inject `static transitive` (it's needed for compilation only)

Refer to https://jspecify.dev/docs/using#if-your-code-already-uses-jsr-305-annotations for differences we might see with this change

Also refer to https://jspecify.dev/docs/tool-conformance ... for more details on tooling support

resolves #649 

Patch:
diff --git a/inject-events/pom.xml b/inject-events/pom.xml
index 5087ca06..90c1c6df 100644
--- a/inject-events/pom.xml
+++ b/inject-events/pom.xml
@@ -12,7 +12,7 @@
     <dependency>
       <groupId>io.avaje</groupId>
       <artifactId>avaje-inject</artifactId>
-      <version>10.0-RC6</version>
+      <version>10.0</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
diff --git a/inject-generator/pom.xml b/inject-generator/pom.xml
index 9c7d866c..9371fd50 100644
--- a/inject-generator/pom.xml
+++ b/inject-generator/pom.xml
@@ -13,7 +13,7 @@
   <name>avaje inject generator</name>
   <description>annotation processor generating source code for avaje-inject dependency injection</description>
   <properties>
-    <avaje.prisms.version>1.28</avaje.prisms.version>
+    <avaje.prisms.version>1.30</avaje.prisms.version>
   </properties>
   <dependencies>
 
@@ -34,7 +34,7 @@
     <dependency>
       <groupId>io.avaje</groupId>
       <artifactId>avaje-spi-service</artifactId>
-      <version>2.1</version>
+      <version>2.2</version>
     </dependency>
     <!-- test dependencies -->
     <dependency>
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/EventPublisherWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/EventPublisherWriter.java
index a4b953be..90875ecd 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/EventPublisherWriter.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/EventPublisherWriter.java
@@ -52,7 +52,7 @@ final class EventPublisherWriter {
       .toString()
       .replaceFirst("java.", "")
       + ".events";
-    qualifier = Optional.ofNullable(Util.getNamed(element)).orElse("");
+    qualifier = Optional.ofNullable(Util.named(element)).orElse("");
     var className =
       packageName
         + "."
@@ -65,7 +65,7 @@ final class EventPublisherWriter {
     if (GENERATED_PUBLISHERS.containsKey(originName)) {
       //in super niche situations when compiling the same module, we need to tell avaje that these types already exist
       //got this when running both my eclipse compiler and then the terminal build
-      ProcessingContext.addOptionalType(UType.parse(asType).fullWithoutAnnotations(), Util.getNamed(element));
+      ProcessingContext.addOptionalType(UType.parse(asType).fullWithoutAnnotations(), Util.named(element));
       return;
     }
     importTypes.addAll(utype.importTypes());
@@ -77,7 +77,7 @@ final class EventPublisherWriter {
     Optional.ofNullable(APContext.typeElement(className)).ifPresent(e ->
       GENERATED_PUBLISHERS.put(
         e.getQualifiedName().toString(),
-        Optional.ofNullable(Util.getNamed(e)).orElse("")));
+        Optional.ofNullable(Util.named(e)).orElse("")));
 
     if (Optional.ofNullable(GENERATED_PUBLISHERS.get(className))
         .filter(not(qualifier::equals))
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java
index b300bd07..05985b59 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java
@@ -19,7 +19,7 @@ final class FieldReader {
 
   FieldReader(Element element) {
     this.element = element;
-    this.name = Util.getNamed(element);
+    this.name = Util.named(element);
     this.nullable = Util.isNullable(element);
     this.isBeanMap = QualifiedMapPrism.isPresent(element);
     this.utype = Util.determineType(element.asType(), isBeanMap);
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java b/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java
index f7a048a8..c7c6de99 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java
@@ -155,7 +155,7 @@ public final class InjectProcessor extends AbstractProcessor {
       .forEach(e -> {
         var type = UType.parse(e.asType());
         type = "java.util.List".equals(type.mainType()) ? type.param0() : type;
-        ProcessingContext.addOptionalType(type.fullWithoutAnnotations(), Util.getNamed(e));
+        ProcessingContext.addOptionalType(type.fullWithoutAnnotations(), Util.named(e));
         ProcessingContext.addOptionalType(type.fullWithoutAnnotations(), null);
       });
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java
index 39e03f58..1bdff7b2 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java
@@ -492,7 +492,7 @@ final class MethodReader {
     MethodParam(VariableElement param) {
       this.element = param;
       this.simpleName = param.getSimpleName().toString();
-      this.named = Util.getNamed(param);
+      this.named = Util.named(param);
       this.nullable = Util.isNullable(param);
       this.isBeanMap = QualifiedMapPrism.isPresent(param);
       this.utilType = Util.determineType(param.asType(), isBeanMap);
@@ -572,7 +572,7 @@ final class MethodReader {
         importTypes.add("io.avaje.inject.events.ObserverManager");
       }
       importTypes.addAll(fullUType.importTypes());
-      Util.getNullableAnnotation(element).map(Object::toString).ifPresent(importTypes::add);
+      Util.nullableAnnotation(element).map(Object::toString).ifPresent(importTypes::add);
     }
 
     void checkRequest(BeanRequestParams requestParams) {
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
index 886b8806..ecf537f6 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java
@@ -129,7 +129,7 @@ final class ProcessingContext {
 
   static void addOptionalType(String paramType, String name) {
     if (!CTX.get().providedTypes.contains(paramType)) {
-      CTX.get().optionalTypes.add(Util.addQualifierSuffixTrim(name, paramType));
+      CTX.get().optionalTypes.add(ProcessorUtils.trimAnnotations(Util.addQualifierSuffixTrim(name, paramType)));
     }
   }
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java
index 82e7230b..5f3e6718 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java
@@ -165,7 +165,7 @@ final class TypeExtendsInjection {
 
 
   private void addFactoryMethod(ExecutableElement methodElement, BeanPrism bean) {
-    String qualifierName = Util.getNamed(methodElement);
+    String qualifierName = Util.named(methodElement);
     factoryMethods.add(new MethodReader(methodElement, baseType, bean, qualifierName, importTypes).read());
   }
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
index 95b8a4f6..3c328737 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
@@ -183,16 +183,6 @@ final class Util {
     return shortName(type.mainType()).toLowerCase();
   }
 
-  String trimExtends(UType uType) {
-    String type = uType.mainType();
-    if (type != null && type.startsWith("? extends ")) {
-      return type.substring(10);
-    } else if ("?".equals(type)) {
-      return "Wildcard";
-    }
-    return type;
-  }
-
   static boolean isOptional(String rawType) {
     return rawType.startsWith(OPTIONAL_PREFIX);
   }
@@ -298,7 +288,7 @@ final class Util {
   /**
    * Return the name via <code>@Named</code> or a Qualifier annotation.
    */
-  static String getNamed(Element p) {
+  static String named(Element p) {
     final NamedPrism named = NamedPrism.getInstanceOn(p);
     if (named != null) {
       return named.value().replace("\"", "\\\"");
@@ -320,8 +310,11 @@ final class Util {
   /**
    * Return true if the element has a Nullable annotation.
    */
-  static boolean isNullable(Element p) {
-    for (final AnnotationMirror mirror : p.getAnnotationMirrors()) {
+  static boolean isNullable(Element element) {
+    if (ProcessorUtils.hasAnnotationWithName(element, NULLABLE)) {
+      return true;
+    }
+    for (final AnnotationMirror mirror : UType.parse(element.asType()).annotations()) {
       if (NULLABLE.equals(shortName(mirror.getAnnotationType().toString()))) {
         return true;
       }
@@ -329,8 +322,8 @@ final class Util {
     return false;
   }
 
-  static Optional<DeclaredType> getNullableAnnotation(Element p) {
-    for (final AnnotationMirror mirror : p.getAnnotationMirrors()) {
+  static Optional<DeclaredType> nullableAnnotation(Element element) {
+    for (final AnnotationMirror mirror : element.getAnnotationMirrors()) {
       if (NULLABLE.equals(shortName(mirror.getAnnotationType().toString()))) {
         return Optional.of(mirror.getAnnotationType());
       }
diff --git a/inject/pom.xml b/inject/pom.xml
index 9a96fc0c..3f39aed9 100644
--- a/inject/pom.xml
+++ b/inject/pom.xml
@@ -12,6 +12,12 @@
   <description>avaje-inject dependency injection library using source code generation</description>
   <dependencies>
 
+    <dependency>
+      <groupId>org.jspecify</groupId>
+      <artifactId>jspecify</artifactId>
+      <version>1.0.0</version>
+    </dependency>
+    
     <dependency>
       <groupId>jakarta.inject</groupId>
       <artifactId>jakarta.inject-api</artifactId>
@@ -24,12 +30,6 @@
       <version>${project.version}</version>
     </dependency>
 
-    <dependency>
-      <groupId>io.avaje</groupId>
-      <artifactId>avaje-lang</artifactId>
-      <version>1.1</version>
-    </dependency>
-
     <dependency>
       <groupId>io.avaje</groupId>
       <artifactId>avaje-applog</artifactId>
@@ -46,7 +46,7 @@
     <dependency>
       <groupId>io.avaje</groupId>
       <artifactId>avaje-spi-service</artifactId>
-      <version>2.1</version>
+      <version>2.2</version>
       <optional>true</optional>
     </dependency>
 
diff --git a/inject/src/main/java/io/avaje/inject/BeanEntry.java b/inject/src/main/java/io/avaje/inject/BeanEntry.java
index c7f6ceda..889757e4 100644
--- a/inject/src/main/java/io/avaje/inject/BeanEntry.java
+++ b/inject/src/main/java/io/avaje/inject/BeanEntry.java
@@ -1,7 +1,5 @@
 package io.avaje.inject;
 
-import io.avaje.lang.NonNullApi;
-
 import java.util.Set;
 
 /**
@@ -9,7 +7,6 @@ import java.util.Set;
  *
  * @see BeanScope#all()
  */
-@NonNullApi
 public interface BeanEntry {
 
   /**
diff --git a/inject/src/main/java/io/avaje/inject/BeanScope.java b/inject/src/main/java/io/avaje/inject/BeanScope.java
index 85fe78b0..0a480b4a 100644
--- a/inject/src/main/java/io/avaje/inject/BeanScope.java
+++ b/inject/src/main/java/io/avaje/inject/BeanScope.java
@@ -6,8 +6,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
-import io.avaje.lang.NonNullApi;
-import io.avaje.lang.Nullable;
+import org.jspecify.annotations.Nullable;
 
 /**
  * Holds beans created by dependency injection.
@@ -67,7 +66,6 @@ import io.avaje.lang.Nullable;
  *
  * }</pre>
  */
-@NonNullApi
 public interface BeanScope extends AutoCloseable {
 
   /**
diff --git a/inject/src/main/java/io/avaje/inject/BeanScopeBuilder.java b/inject/src/main/java/io/avaje/inject/BeanScopeBuilder.java
index a438aaba..cad20dc3 100644
--- a/inject/src/main/java/io/avaje/inject/BeanScopeBuilder.java
+++ b/inject/src/main/java/io/avaje/inject/BeanScopeBuilder.java
@@ -1,15 +1,15 @@
 package io.avaje.inject;
 
-import io.avaje.inject.spi.AvajeModule;
-import io.avaje.inject.spi.ConfigPropertyPlugin;
-import io.avaje.inject.spi.PropertyRequiresPlugin;
-import io.avaje.lang.NonNullApi;
-import io.avaje.lang.Nullable;
-
 import java.lang.reflect.Type;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
+import org.jspecify.annotations.Nullable;
+
+import io.avaje.inject.spi.AvajeModule;
+import io.avaje.inject.spi.ConfigPropertyPlugin;
+import io.avaje.inject.spi.PropertyRequiresPlugin;
+
 /**
  * Build a bean scope with options for shutdown hook and supplying external dependencies.
  * <p>
@@ -30,7 +30,6 @@ import java.util.function.Supplier;
  *
  * }</pre>
  */
-@NonNullApi
 public interface BeanScopeBuilder {
 
   /**
diff --git a/inject/src/main/java/io/avaje/inject/DBeanScopeBuilder.java b/inject/src/main/java/io/avaje/inject/DBeanScopeBuilder.java
index 58d95dad..186ed469 100644
--- a/inject/src/main/java/io/avaje/inject/DBeanScopeBuilder.java
+++ b/inject/src/main/java/io/avaje/inject/DBeanScopeBuilder.java
@@ -3,6 +3,7 @@ package io.avaje.inject;
 import static java.lang.System.Logger.Level.DEBUG;
 import static java.lang.System.Logger.Level.INFO;
 import static java.util.Collections.emptySet;
+
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -14,14 +15,20 @@ import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
+import org.jspecify.annotations.Nullable;
+
 import io.avaje.applog.AppLog;
-import io.avaje.inject.spi.*;
-import io.avaje.lang.NonNullApi;
-import io.avaje.lang.Nullable;
+import io.avaje.inject.spi.AvajeModule;
+import io.avaje.inject.spi.Builder;
+import io.avaje.inject.spi.ClosePair;
+import io.avaje.inject.spi.ConfigPropertyPlugin;
+import io.avaje.inject.spi.EnrichBean;
+import io.avaje.inject.spi.ModuleOrdering;
+import io.avaje.inject.spi.PropertyRequiresPlugin;
+import io.avaje.inject.spi.SuppliedBean;
 import jakarta.inject.Provider;
 
 /** Build a bean scope with options for shutdown hook and supplying test doubles. */
-@NonNullApi
 final class DBeanScopeBuilder implements BeanScopeBuilder.ForTesting {
 
   private static final System.Logger log = AppLog.getLogger("io.avaje.inject");
diff --git a/inject/src/main/java/io/avaje/inject/package-info.java b/inject/src/main/java/io/avaje/inject/package-info.java
index d86ae4f7..1d14d17e 100644
--- a/inject/src/main/java/io/avaje/inject/package-info.java
+++ b/inject/src/main/java/io/avaje/inject/package-info.java
@@ -26,4 +26,5 @@
  *
  * }</pre>
  */
+@org.jspecify.annotations.NullMarked
 package io.avaje.inject;
diff --git a/inject/src/main/java/io/avaje/inject/spi/ConfigPropertyPlugin.java b/inject/src/main/java/io/avaje/inject/spi/ConfigPropertyPlugin.java
index 61c7bb47..29850b0b 100644
--- a/inject/src/main/java/io/avaje/inject/spi/ConfigPropertyPlugin.java
+++ b/inject/src/main/java/io/avaje/inject/spi/ConfigPropertyPlugin.java
@@ -2,7 +2,7 @@ package io.avaje.inject.spi;
 
 import java.util.Optional;
 
-import io.avaje.lang.NonNullApi;
+import org.jspecify.annotations.NullMarked;
 
 /**
  * Plugin interface which contains the application properties used for wiring. Used with
@@ -11,28 +11,33 @@ import io.avaje.lang.NonNullApi;
  * <p>The plugin is loaded via ServiceLoader and defaults to an implementation that uses {@link
  * System#getProperty(String)} and {@link System#getenv(String)}.
  */
-@NonNullApi
+@NullMarked
 public interface ConfigPropertyPlugin extends InjectExtension, PropertyRequiresPlugin {
 
   /**
    * Return a configuration value that might not exist.
    */
+  @Override
   Optional<String> get(String property);
 
   /**
    * Return true if the property is defined.
    */
+  @Override
   boolean contains(String property);
 
   /** Return true if the property is not defined. */
+  @Override
   default boolean missing(String property) {
     return !contains(property);
   }
 
   /** Return true if the property is equal to the given value. */
+  @Override
   boolean equalTo(String property, String value);
 
   /** Return true if the property is not defined or not equal to the given value. */
+  @Override
   default boolean notEqualTo(String property, String value) {
     return !equalTo(property, value);
   }
diff --git a/inject/src/main/java/io/avaje/inject/spi/DBeanScope.java b/inject/src/main/java/io/avaje/inject/spi/DBeanScope.java
index 26308792..430a081a 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DBeanScope.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DBeanScope.java
@@ -1,24 +1,31 @@
 package io.avaje.inject.spi;
 
-import io.avaje.applog.AppLog;
-import io.avaje.inject.BeanEntry;
-import io.avaje.inject.BeanScope;
-import io.avaje.inject.Priority;
-import io.avaje.lang.NonNullApi;
-import io.avaje.lang.Nullable;
+import static java.lang.System.Logger.Level.INFO;
+import static java.lang.System.Logger.Level.TRACE;
 
 import java.lang.System.Logger.Level;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Optional;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
-import static java.lang.System.Logger.Level.INFO;
-import static java.lang.System.Logger.Level.TRACE;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+import io.avaje.applog.AppLog;
+import io.avaje.inject.BeanEntry;
+import io.avaje.inject.BeanScope;
+import io.avaje.inject.Priority;
 
-@NonNullApi
+@NullMarked
 final class DBeanScope implements BeanScope {
 
   private static final System.Logger log = AppLog.getLogger("io.avaje.inject");
diff --git a/inject/src/main/java/io/avaje/inject/spi/DEntry.java b/inject/src/main/java/io/avaje/inject/spi/DEntry.java
index 7ab1f0c7..a9239d58 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DEntry.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DEntry.java
@@ -1,12 +1,13 @@
 package io.avaje.inject.spi;
 
-import io.avaje.inject.BeanEntry;
-import io.avaje.lang.NonNullApi;
-
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-@NonNullApi
+import org.jspecify.annotations.NullMarked;
+
+import io.avaje.inject.BeanEntry;
+
+@NullMarked
 final class DEntry implements BeanEntry {
 
   private final String qualifierName;
diff --git a/inject/src/main/java/io/avaje/inject/spi/PropertyRequiresPlugin.java b/inject/src/main/java/io/avaje/inject/spi/PropertyRequiresPlugin.java
index ed06cf23..f7f03c5e 100644
--- a/inject/src/main/java/io/avaje/inject/spi/PropertyRequiresPlugin.java
+++ b/inject/src/main/java/io/avaje/inject/spi/PropertyRequiresPlugin.java
@@ -2,7 +2,7 @@ package io.avaje.inject.spi;
 
 import java.util.Optional;
 
-import io.avaje.lang.NonNullApi;
+import org.jspecify.annotations.NullMarked;
 
 /**
  * Plugin interface which contains the application properties used for wiring. Used with {@link
@@ -13,7 +13,7 @@ import io.avaje.lang.NonNullApi;
  *
  * @deprecated use ConfigPropertyPlugin Instead
  */
-@NonNullApi
+@NullMarked
 @Deprecated(forRemoval = true)
 public interface PropertyRequiresPlugin extends InjectExtension {
 
diff --git a/inject/src/main/java/module-info.java b/inject/src/main/java/module-info.java
index a62bdcfd..ae9beb14 100644
--- a/inject/src/main/java/module-info.java
+++ b/inject/src/main/java/module-info.java
@@ -3,13 +3,14 @@ module io.avaje.inject {
   exports io.avaje.inject;
   exports io.avaje.inject.spi;
 
-  requires transitive io.avaje.lang;
   requires transitive io.avaje.applog;
-  requires transitive jakarta.inject;
   requires static io.avaje.config;
   requires static org.mockito;
   requires static io.avaje.spi;
 
+  requires static transitive jakarta.inject;
+  requires static transitive org.jspecify;
+
   uses io.avaje.inject.spi.InjectExtension;
   uses io.avaje.inject.spi.Module;
   uses io.avaje.inject.spi.Plugin;


Test patch:
diff --git a/blackbox-test-inject/pom.xml b/blackbox-test-inject/pom.xml
index c56b672c..bc682126 100644
--- a/blackbox-test-inject/pom.xml
+++ b/blackbox-test-inject/pom.xml
@@ -52,7 +52,7 @@
     <dependency>
       <groupId>io.avaje</groupId>
       <artifactId>avaje-spi-service</artifactId>
-      <version>2.1</version>
+      <version>2.2</version>
     </dependency>
 
     <dependency>
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/external/ExternalDeps.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/external/ExternalDeps.java
index daa1e556..5479d1a7 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/external/ExternalDeps.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/external/ExternalDeps.java
@@ -2,13 +2,17 @@ package io.avaje.inject.generator.models.valid.external;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.jspecify.annotations.Nullable;
 
 import io.avaje.inject.External;
-import io.avaje.lang.Nullable;
+import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
 
 @Singleton
 public class ExternalDeps {
+  @Inject @Nullable AtomicLong longy;
 
   public ExternalDeps(@External AtomicBoolean bool, @Nullable AtomicInteger inty) {}
 }
diff --git a/inject-test/src/main/java/io/avaje/inject/test/GlobalInitialise.java b/inject-test/src/main/java/io/avaje/inject/test/GlobalInitialise.java
index 457ff387..f1b2b438 100644
--- a/inject-test/src/main/java/io/avaje/inject/test/GlobalInitialise.java
+++ b/inject-test/src/main/java/io/avaje/inject/test/GlobalInitialise.java
@@ -2,7 +2,7 @@ package io.avaje.inject.test;
 
 import io.avaje.inject.BeanScope;
 import io.avaje.inject.spi.AvajeModule;
-import io.avaje.lang.Nullable;
+import org.jspecify.annotations.Nullable;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/inject-test/src/main/java/io/avaje/inject/test/PluginMgr.java b/inject-test/src/main/java/io/avaje/inject/test/PluginMgr.java
index a6c955d5..c5daf3ac 100644
--- a/inject-test/src/main/java/io/avaje/inject/test/PluginMgr.java
+++ b/inject-test/src/main/java/io/avaje/inject/test/PluginMgr.java
@@ -1,10 +1,12 @@
 package io.avaje.inject.test;
 
-import io.avaje.inject.BeanScope;
-import io.avaje.lang.Nullable;
-
 import java.util.ServiceLoader;
 
+import org.jspecify.annotations.Nullable;
+
+import io.avaje.inject.BeanScope;
+import io.avaje.inject.test.Plugin.Scope;
+
 final class PluginMgr {
 
   private PluginMgr() {}
@@ -23,8 +25,8 @@ final class PluginMgr {
   /**
    * Return a new plugin scope (if there is a plugin).
    */
-   @Nullable
-  static Plugin.Scope scope(BeanScope beanScope) {
+  @Nullable
+  static Scope scope(BeanScope beanScope) {
     return plugin == null ? null : plugin.createScope(beanScope);
   }
 }
diff --git a/inject-test/src/main/java/io/avaje/inject/test/TestBeanScope.java b/inject-test/src/main/java/io/avaje/inject/test/TestBeanScope.java
index fb91f5d9..e8803433 100644
--- a/inject-test/src/main/java/io/avaje/inject/test/TestBeanScope.java
+++ b/inject-test/src/main/java/io/avaje/inject/test/TestBeanScope.java
@@ -1,15 +1,16 @@
 package io.avaje.inject.test;
 
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
 import io.avaje.inject.BeanScope;
 import io.avaje.inject.BeanScopeBuilder;
-import io.avaje.lang.NonNullApi;
-import io.avaje.lang.Nullable;
 
 /**
  * Provides access to the global "test scope" and helper methods to use it.
  *
  */
-@NonNullApi
+@NullMarked
 public abstract class TestBeanScope {
 
   /**