Repository: avaje/avaje-inject
Pull request #226 (2022-06-03 04:57:46)

Injecting Generics refactor and fix


Patch:
diff --git a/inject-generator/pom.xml b/inject-generator/pom.xml
index d3f474cc..47597f94 100644
--- a/inject-generator/pom.xml
+++ b/inject-generator/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>io.avaje</groupId>
     <artifactId>avaje-inject-parent</artifactId>
-    <version>8.5</version>
+    <version>8.6</version>
   </parent>
 
   <artifactId>avaje-inject-generator</artifactId>
@@ -16,7 +16,7 @@
     <dependency>
       <groupId>io.avaje</groupId>
       <artifactId>avaje-inject</artifactId>
-      <version>8.5</version>
+      <version>8.6</version>
     </dependency>
 
     <!-- test dependencies -->
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
index 3db9d531..ffc55d0f 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
@@ -34,6 +34,7 @@ class BeanReader {
   private boolean writtenToFile;
   private boolean suppressBuilderImport;
   private boolean suppressGeneratedImport;
+  private Set<GenericType> allGenericTypes;
 
   BeanReader(TypeElement beanType, ProcessingContext context, boolean factory) {
     this.beanType = beanType;
@@ -43,8 +44,7 @@ class BeanReader {
     this.primary = (beanType.getAnnotation(Primary.class) != null);
     this.secondary = !primary && (beanType.getAnnotation(Secondary.class) != null);
     this.proxy = (beanType.getAnnotation(Proxy.class) != null);
-    this.typeReader = new TypeReader(beanType, context, importTypes, factory);
-
+    this.typeReader = new TypeReader(GenericType.parse(type), beanType, context, importTypes, factory);
     typeReader.process();
     this.requestParams = new BeanRequestParams(type);
     this.name = typeReader.getName();
@@ -79,13 +79,13 @@ class BeanReader {
       constructor.addImports(importTypes);
       constructor.checkRequest(requestParams);
     }
-    for (FieldReader fields : injectFields) {
-      fields.addImports(importTypes);
-      fields.checkRequest(requestParams);
+    for (FieldReader field : injectFields) {
+      field.addImports(importTypes);
+      field.checkRequest(requestParams);
     }
-    for (MethodReader methods : injectMethods) {
-      methods.addImports(importTypes);
-      methods.checkRequest(requestParams);
+    for (MethodReader method : injectMethods) {
+      method.addImports(importTypes);
+      method.checkRequest(requestParams);
     }
     for (MethodReader factoryMethod : factoryMethods) {
       factoryMethod.addImports(importTypes);
@@ -115,6 +115,26 @@ class BeanReader {
     return typeReader.getGenericTypes();
   }
 
+  Set<GenericType> allGenericTypes() {
+    if (allGenericTypes != null) {
+      return allGenericTypes;
+    }
+    allGenericTypes = new LinkedHashSet<>(typeReader.getGenericTypes());
+    for (FieldReader field : injectFields) {
+      field.addDependsOnGeneric(allGenericTypes);
+    }
+    for (MethodReader method : injectMethods) {
+      method.addDependsOnGeneric(allGenericTypes);
+    }
+    if (constructor != null) {
+      constructor.addDependsOnGeneric(allGenericTypes);
+    }
+    for (MethodReader factoryMethod : getFactoryMethods()) {
+      factoryMethod.addDependsOnGeneric(allGenericTypes);
+    }
+    return allGenericTypes;
+  }
+
   /**
    * Return the short name of the element.
    */
@@ -259,6 +279,10 @@ class BeanReader {
   }
 
   void writeImports(Append writer) {
+    if (!allGenericTypes().isEmpty()) {
+      importTypes.add(Constants.TYPE);
+      importTypes.add(Constants.GENERICTYPE);
+    }
     for (String importType : importTypes()) {
       if (Util.validImportType(importType)) {
         writer.append("import %s;", importType).eol();
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 561d9263..8b9a3996 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
@@ -1,15 +1,17 @@
 package io.avaje.inject.generator;
 
 import javax.lang.model.element.Element;
+import java.util.List;
 import java.util.Set;
 
 class FieldReader {
 
   private final Element element;
   private final String name;
-  private final UtilType type;
+  private final UtilType utype;
   private final boolean nullable;
   private final String fieldType;
+  private final GenericType type;
   private boolean requestParam;
   private String requestParamName;
 
@@ -17,8 +19,19 @@ class FieldReader {
     this.element = element;
     this.name = Util.getNamed(element);
     this.nullable = Util.isNullable(element);
-    this.type = Util.determineType(element.asType());
-    this.fieldType = Util.unwrapProvider(type.rawType());
+    this.utype = Util.determineType(element.asType());
+    this.fieldType = Util.unwrapProvider(utype.rawType());
+    this.type = GenericType.parse(utype.rawType());
+  }
+
+  boolean isGenericParam() {
+    return type.isGenericType() && !type.isProviderType();
+  }
+
+  void addDependsOnGeneric(Set<GenericType> set) {
+    if (isGenericParam()) {
+      set.add(type);
+    }
   }
 
   String getFieldName() {
@@ -26,13 +39,17 @@ class FieldReader {
   }
 
   void addImports(Set<String> importTypes) {
-    importTypes.add(fieldType);
+    type.addImports(importTypes);
   }
 
   String builderGetDependency(String builder) {
     StringBuilder sb = new StringBuilder();
-    sb.append(builder).append(".").append(type.getMethod(nullable));
-    sb.append(nm(fieldType)).append(".class");
+    sb.append(builder).append(".").append(utype.getMethod(nullable));
+    if (isGenericParam()) {
+      sb.append("TYPE_").append(type.shortName());
+    } else {
+      sb.append(Util.shortName(fieldType)).append(".class");
+    }
     if (name != null) {
       sb.append(",\"").append(name).append("\"");
     }
@@ -40,13 +57,17 @@ class FieldReader {
     return sb.toString();
   }
 
+  void removeFromProvides(List<String> provides) {
+    provides.remove(type.toString());
+  }
+
   /**
    * Check for request scoped dependency.
    */
   void checkRequest(BeanRequestParams requestParams) {
-    requestParam = requestParams.check(type.rawType());
+    requestParam = requestParams.check(utype.rawType());
     if (requestParam) {
-      requestParamName = requestParams.argumentName(type.rawType());
+      requestParamName = requestParams.argumentName(utype.rawType());
     }
   }
 
@@ -57,9 +78,9 @@ class FieldReader {
     if (!requestParam) {
       // just add as field dependency
       requestParamName = writer.nextName(getFieldName().toLowerCase());
-      final String shortType = nm(type.rawType());
-      writer.append("  @Inject").eol();
-      writer.append("  %s %s;", shortType, requestParamName).eol().eol();
+      writer.append("  @Inject").eol().append("  ");
+      type.writeShort(writer);
+      writer.append(" %s;", requestParamName).eol().eol();
     }
   }
 
@@ -70,8 +91,4 @@ class FieldReader {
     writer.append("    bean.%s = %s;", getFieldName(), requestParamName).eol();
   }
 
-  private String nm(String raw) {
-    return Util.shortName(raw);
-  }
-
 }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/GenericType.java b/inject-generator/src/main/java/io/avaje/inject/generator/GenericType.java
index d6013c23..319bacb8 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/GenericType.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/GenericType.java
@@ -2,6 +2,7 @@ package io.avaje.inject.generator;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -16,10 +17,26 @@ class GenericType {
     if (rawType.endsWith("<?>")) {
       return rawType.substring(0, rawType.length() - 3);
     } else {
-      return rawType;
+      return trimGenericParams(rawType);
     }
   }
 
+  /**
+   * Trim off generic type parameters.
+   */
+  static String trimGenericParams(String rawType) {
+    int start = rawType.indexOf('<');
+    if (start > 0) {
+      // no package for any generic parameter types
+      if (rawType.indexOf('.', start) == -1) {
+        if (rawType.lastIndexOf('>') > -1) {
+          return rawType.substring(0, start);
+        }
+      }
+    }
+    return rawType;
+  }
+
   private final String raw;
   private String mainType;
 
@@ -62,36 +79,24 @@ class GenericType {
    */
   static String removeParameter(String raw) {
     final GenericType type = parse(raw);
-    return type.hasParameter() ? type.getMainType() : raw;
+    return type.topType();
   }
 
-  /**
-   * Parse and return as GenericType or null if it is not generic.
-   */
-  static GenericType maybe(String paramType) {
-    return isGeneric(paramType) ? parse(paramType) : null;
-  }
 
   @Override
   public String toString() {
     return raw != null ? raw : mainType + '<' + params + '>';
   }
 
-  /**
-   * Return true if the type contains a type parameter like {@code <T>}.
-   */
-  boolean hasParameter() {
-    if (mainType != null && mainType.indexOf('.') == -1) {
-      return true;
-    }
-    for (GenericType param : params) {
-      if (param.hasParameter()) {
-        return true;
-      }
-    }
-    return false;
+  boolean isGenericType() {
+    return !params.isEmpty();
+  }
+
+  boolean isProviderType() {
+    return raw.startsWith(Util.PROVIDER_PREFIX);
   }
 
+
   void addImports(Set<String> importTypes) {
     final String type = trimExtends();
     if (includeInImports(type)) {
@@ -110,10 +115,8 @@ class GenericType {
    * Append the short version of the type (given the type and parameters are in imports).
    */
   void writeShort(Append writer) {
-
     String main = Util.shortName(trimExtends());
     writer.append(main);
-
     final int paramCount = params.size();
     if (paramCount > 0) {
       writer.append("<");
@@ -142,7 +145,7 @@ class GenericType {
 
   private String trimExtends() {
     String type = topType();
-    if (type != null &&  type.startsWith("? extends ")) {
+    if (type != null && type.startsWith("? extends ")) {
       return type.substring(10);
     }
     return type;
@@ -174,4 +177,17 @@ class GenericType {
     params.add(param);
   }
 
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    GenericType that = (GenericType) o;
+    return raw.equals(that.raw);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(raw);
+  }
 }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/GenericTypeParser.java b/inject-generator/src/main/java/io/avaje/inject/generator/GenericTypeParser.java
index fbe65c62..93450ed6 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/GenericTypeParser.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/GenericTypeParser.java
@@ -2,12 +2,10 @@ package io.avaje.inject.generator;
 
 import java.util.Stack;
 
-class GenericTypeParser {
+final class GenericTypeParser {
 
   private final String raw;
-
   private StringBuilder buf = new StringBuilder();
-
   private final Stack<GenericType> stack = new Stack<>();
 
   GenericTypeParser(String raw) {
@@ -16,12 +14,9 @@ class GenericTypeParser {
   }
 
   GenericType parse() {
-    int len = raw.length();
-    for (int i = 0; i < len; i++) {
-      char ch = raw.charAt(i);
-      processChar(ch);
+    for (int i = 0, len = raw.length(); i < len; i++) {
+      processChar(raw.charAt(i));
     }
-
     return stack.peek();
   }
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/MetaData.java b/inject-generator/src/main/java/io/avaje/inject/generator/MetaData.java
index 8e31f602..8a53b3fc 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/MetaData.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/MetaData.java
@@ -132,18 +132,9 @@ class MetaData {
   void addImportTypes(Set<String> importTypes) {
     if (hasMethod()) {
       importTypes.add(Util.classOfMethod(method));
-
     } else {
       importTypes.add(type + Constants.DI);
     }
-    if (provides != null) {
-      for (String provide : provides) {
-        if (GenericType.isGeneric(provide)) {
-          // provide implementation of generic interface
-          importTypes.add(type);
-        }
-      }
-    }
   }
 
   String buildMethod(MetaDataOrdering ordering) {
@@ -168,26 +159,11 @@ class MetaData {
     } else {
       sb.append("    ").append(shortType).append(Constants.DI).append(".build(builder");
     }
-
-    dependsOn.stream().map(Dependency::getName).forEach(depend-> {
-      if (GenericType.isGeneric(depend) && !Util.isProvider(depend)) {
-        // provide implementation of generic interface as a parameter to the build method
-        final MetaData providerMeta = findProviderOf(depend, ordering);
-        if (providerMeta != null) {
-          final GenericType type = GenericType.parse(depend);
-          sb.append(", ").append(providerMeta.shortType).append("$DI.provider").append(type.shortName()).append("(builder)");
-        }
-      }
-    });
     sb.append(");").append(NEWLINE);
     sb.append("  }").append(NEWLINE);
     return sb.toString();
   }
 
-  private MetaData findProviderOf(String depend, MetaDataOrdering ordering) {
-    return ordering.findProviderOf(depend);
-  }
-
   private boolean hasMethod() {
     return method != null && !method.isEmpty();
   }
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 6692b157..f4db6d2b 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
@@ -58,13 +58,13 @@ class MethodReader {
     String initMethod = (bean == null) ? null : bean.initMethod();
     String destroyMethod = (bean == null) ? null : bean.destroyMethod();
     this.name = (named == null) ? null : named.value().toLowerCase();
-    TypeElement returnElement = context.element(topType);
+    TypeElement returnElement = (TypeElement)context.asElement(returnMirror);
     if (returnElement == null) {
       this.typeReader = null;
       this.initMethod = initMethod;
       this.destroyMethod = destroyMethod;
     } else {
-      this.typeReader = new TypeReader(returnElement, context);
+      this.typeReader = new TypeReader(genericType, returnElement, context);
       typeReader.process();
       MethodLifecycleReader lifecycleReader = new MethodLifecycleReader(returnElement, initMethod, destroyMethod);
       this.initMethod = lifecycleReader.initMethod();
@@ -72,6 +72,26 @@ class MethodReader {
     }
   }
 
+  @Override
+  public String toString() {
+    return "MethodReader{" +
+      "element=" + element +
+      ", params=" + params +
+      '}';
+  }
+
+  void addDependsOnGeneric(Set<GenericType> set) {
+    for (MethodParam param : params) {
+      param.addDependsOnGeneric(set);
+    }
+    if (genericType.isGenericType()) {
+      set.add(genericType);
+    }
+    if (typeReader != null) {
+      set.addAll(typeReader.getGenericTypes());
+    }
+  }
+
   MethodReader read() {
     List<? extends VariableElement> ps = element.getParameters();
     for (VariableElement p : ps) {
@@ -110,25 +130,23 @@ class MethodReader {
     return String.format("      %s factory = builder.get(%s.class);", factoryShortName, factoryShortName);
   }
 
-  String builderBuildBean() {
-    StringBuilder sb = new StringBuilder();
-    sb.append("      ");
+  void builderBuildBean(Append writer) {
+    writer.append("      ");
     if (isVoid) {
-      sb.append(String.format("factory.%s(", methodName));
+      writer.append(String.format("factory.%s(", methodName));
     } else {
       String beanType = optionalType ? String.format("Optional<%s>", shortName) : shortName;
       String beanName = optionalType ? "optionalBean" : "bean";
-      sb.append(beanType);
-      sb.append(String.format(" %s = factory.%s(", beanName, methodName));
+      writer.append(beanType);
+      writer.append(String.format(" %s = factory.%s(", beanName, methodName));
     }
     for (int i = 0; i < params.size(); i++) {
       if (i > 0) {
-        sb.append(", ");
+        writer.append(", ");
       }
-      sb.append(params.get(i).builderGetDependency("builder", true));
+      params.get(i).builderGetDependency(writer, "builder", true);
     }
-    sb.append(");");
-    return sb.toString();
+    writer.append(");").eol();
   }
 
   public void builderAddProtoBean(Append writer) {
@@ -149,7 +167,7 @@ class MethodReader {
       if (i > 0) {
         writer.append(", ");
       }
-      writer.append(params.get(i).builderGetDependency("builder", true));
+      params.get(i).builderGetDependency(writer, "builder", true);
     }
     writer.append(");").eol();
     writer.append(indent).append("  });").eol();
@@ -283,21 +301,10 @@ class MethodReader {
     }
   }
 
-  void writeAspectBefore(Append writer, String targetName) {
-    writer.append("    // %s.%s( ... ", targetName, methodName);
-    for (int i = 0, size = params.size(); i < size; i++) {
-      if (i > 0) {
-        writer.append(", ");
-      }
-      params.get(i).writeConstructorInit(writer);
+  void removeFromProvides(List<String> provides) {
+    for (MethodParam param : params) {
+      param.removeFromProvides(provides);
     }
-    writer.append(");", targetName, methodName);
-    writer.eol();
-  }
-
-  void writeAspectAround(Append writer, String targetName) {
-    writer.append("    // %s %s aroundAspect", targetName, methodName);
-    writer.eol();
   }
 
   static class MethodParam {
@@ -308,7 +315,6 @@ class MethodReader {
     private final GenericType genericType;
     private final boolean nullable;
     private final String simpleName;
-    private int providerIndex;
     private boolean requestParam;
     private String requestParamName;
 
@@ -318,39 +324,45 @@ class MethodReader {
       this.nullable = Util.isNullable(param);
       this.utilType = Util.determineType(param.asType());
       this.paramType = utilType.rawType();
-      this.genericType = GenericType.maybe(paramType);
+      this.genericType = GenericType.parse(paramType);
     }
 
-    String builderGetDependency(String builderName, boolean forFactory) {
-      StringBuilder sb = new StringBuilder();
-      if (!forFactory && isGenericParam()) {
-        // passed as provider to build method
-        sb.append("prov").append(providerIndex).append(".get(");
-      } else {
-        sb.append(builderName).append(".").append(utilType.getMethod(nullable));
+    @Override
+    public String toString() {
+      return "MethodParam{" +
+        "genericType=" + genericType +
+        '}';
+    }
+
+    void addDependsOnGeneric(Set<GenericType> set) {
+      if (genericType.isGenericType() && !genericType.isProviderType()) {
+        set.add(genericType);
       }
-      if (genericType == null) {
-        sb.append(Util.shortName(paramType)).append(".class");
+    }
+
+    void builderGetDependency(Append writer, String builderName, boolean forFactory) {
+      writer.append(builderName).append(".").append(utilType.getMethod(nullable));
+      if (!genericType.isGenericType()) {
+        writer.append(Util.shortName(genericType.topType())).append(".class");
       } else if (isProvider()) {
-        sb.append(providerParam()).append(".class");
-      } else if (forFactory) {
-        sb.append(Util.shortName(genericType.topType())).append(".class");
+        writer.append(providerParam()).append(".class");
+      } else {
+        writer.append("TYPE_").append(genericType.shortName());
       }
       if (named != null && !named.isEmpty()) {
-        sb.append(",\"").append(named).append("\"");
+        writer.append(",\"").append(named).append("\"");
       } else if (!isGenericParam() && utilType.allowsNamedQualifier()) {
         // implied qualifier name, leading '!' means implied
-        sb.append(",\"!");
+        writer.append(",\"!");
         final String shortName = Util.shortName(paramType);
         if (simpleName.endsWith(shortName)) {
-          sb.append(simpleName, 0, simpleName.length() - shortName.length());
+          writer.append(simpleName, 0, simpleName.length() - shortName.length());
         } else {
-          sb.append(simpleName);
+          writer.append(simpleName);
         }
-        sb.append("\"");
+        writer.append("\"");
       }
-      sb.append(")");
-      return sb.toString();
+      writer.append(")");
     }
 
     private String providerParam() {
@@ -366,7 +378,7 @@ class MethodReader {
     }
 
     boolean isGenericType() {
-      return genericType != null;
+      return genericType.isGenericType();
     }
 
     boolean isGenericParam() {
@@ -378,22 +390,14 @@ class MethodReader {
     }
 
     void addImports(Set<String> importTypes) {
-      if (genericType != null) {
+      if (genericType.isGenericType()) {
         importTypes.add(Constants.PROVIDER);
         genericType.addImports(importTypes);
       } else {
-        importTypes.add(paramType);
+        genericType.addImports(importTypes);
       }
     }
 
-    void addProviderParam(Append writer, int providerIndex) {
-      this.providerIndex = providerIndex;
-      writer.append(", ");
-      writer.append("Provider<");
-      genericType.writeShort(writer);
-      writer.append("> prov%s", providerIndex);
-    }
-
     void checkRequest(BeanRequestParams requestParams) {
       requestParam = requestParams.check(paramType);
       if (requestParam) {
@@ -403,10 +407,11 @@ class MethodReader {
 
     void writeRequestDependency(Append writer) {
       if (!requestParam) {
-        final String shortType = nm(paramType);
-        requestParamName = writer.nextName(shortType.toLowerCase());
+        requestParamName = writer.nextName(Util.trimmedName(genericType));
         writer.append("  @Inject").eol();
-        writer.append("  %s %s;", shortType, requestParamName).eol().eol();
+        writer.append("  ");
+        genericType.writeShort(writer);
+        writer.append(" %s;", requestParamName).eol().eol();
       }
     }
 
@@ -414,30 +419,25 @@ class MethodReader {
       writer.commaAppend(requestParamName);
     }
 
-    private String nm(String raw) {
-      return Util.shortName(raw);
-    }
-
     void writeMethodParam(Append writer) {
-      if (genericType != null) {
+      if (genericType.isGenericType()) {
         genericType.writeShort(writer);
       } else {
-        writer.append(Util.shortName(paramType));
+        writer.append(Util.shortName(genericType.topType()));
       }
       writer.append(" ").append(simpleName);
     }
 
     void writeMethodParamType(Append writer) {
-      if (genericType != null) {
-        // for getDeclaredMethod only need top level main type
-        writer.append(genericType.getMainType());
-      } else {
-        writer.append(Util.shortName(paramType));
-      }
+      writer.append(Util.shortName(genericType.topType()));
     }
 
     void writeConstructorInit(Append writer) {
       writer.append(simpleName);
     }
+
+    void removeFromProvides(List<String> provides) {
+      provides.remove(genericType.toString());
+    }
   }
 }
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 57a743ad..b2ab6654 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
@@ -115,6 +115,10 @@ class ProcessingContext {
     return elementUtils.getTypeElement(rawType);
   }
 
+  Types types() {
+    return typeUtils;
+  }
+
   TypeElement elementMaybe(String rawType) {
     if (rawType == null) {
       return null;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Processor.java b/inject-generator/src/main/java/io/avaje/inject/generator/Processor.java
index 484f80a7..c0b5b639 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Processor.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Processor.java
@@ -17,7 +17,6 @@ import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.Elements;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
@@ -61,8 +60,8 @@ public class Processor extends AbstractProcessor {
 
   @Override
   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
-    readScopes(roundEnv.getElementsAnnotatedWith(Scope.class));
     readModule(roundEnv);
+    readScopes(roundEnv.getElementsAnnotatedWith(Scope.class));
     readChangedBeans(roundEnv.getElementsAnnotatedWith(Factory.class), true);
     if (defaultScope.includeSingleton()) {
       readChangedBeans(roundEnv.getElementsAnnotatedWith(Singleton.class), false);
@@ -166,18 +165,13 @@ public class Processor extends AbstractProcessor {
    */
   private void readInjectModule(RoundEnvironment roundEnv) {
     // read other that are annotated with InjectModule
-    Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(InjectModule.class);
-    if (!elementsAnnotatedWith.isEmpty()) {
-      Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
-      if (iterator.hasNext()) {
-        Element element = iterator.next();
-        Scope scope = element.getAnnotation(Scope.class);
-        if (scope == null) {
-          // it it not a custom scope annotation
-          InjectModule annotation = element.getAnnotation(InjectModule.class);
-          if (annotation != null) {
-            defaultScope.details(annotation.name(), element);
-          }
+    for (Element element : roundEnv.getElementsAnnotatedWith(InjectModule.class)) {
+      Scope scope = element.getAnnotation(Scope.class);
+      if (scope == null) {
+        // it it not a custom scope annotation
+        InjectModule annotation = element.getAnnotation(InjectModule.class);
+        if (annotation != null) {
+          defaultScope.details(annotation.name(), element);
         }
       }
     }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
index a52e69bc..4408a05b 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
@@ -398,7 +398,6 @@ class ScopeInfo {
   }
 
   void readModuleMetaData(TypeElement moduleType) {
-    context.logDebug("Reading module info for " + moduleType);
     InjectModule module = moduleType.getAnnotation(InjectModule.class);
     details(module.name(), moduleType);
     readFactoryMetaData(moduleType);
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java
index 0d4a9e6f..c6aa0ed9 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java
@@ -4,7 +4,6 @@ import javax.lang.model.element.TypeElement;
 import javax.tools.JavaFileObject;
 import java.io.IOException;
 import java.io.Writer;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -61,7 +60,6 @@ class SimpleBeanWriter {
       writeRequestCreate();
     } else {
       writeGenericTypeFields();
-      writeGenericProviders();
       writeStaticFactoryMethod();
       writeStaticFactoryBeanMethods();
     }
@@ -69,35 +67,14 @@ class SimpleBeanWriter {
     writer.close();
   }
 
-  private void writeGenericProviders() {
-    final Set<GenericType> genericTypes = beanReader.getGenericTypes();
-    if (genericTypes != null && !genericTypes.isEmpty()) {
-      for (GenericType type : genericTypes) {
-        final String sn = type.shortName();
-        writer.append("  public static Provider<");
-        type.writeShort(writer);
-        writer.append("> provider%s(Builder builder) {", sn).eol();
-        writer.append("    return builder.getProviderFor(%s.class, TYPE_%s);", shortName, sn).eol();
-        writer.append("  }").eol();
-      }
-      writer.eol();
-    }
-  }
-
   private void writeGenericTypeFields() {
     // collect all types to prevent duplicates
-    Set<GenericType> genericTypes = new HashSet<>();
-    if (beanReader.getGenericTypes()!=null) {
-      genericTypes.addAll(beanReader.getGenericTypes());
-    }
-    for (MethodReader factoryMethod : beanReader.getFactoryMethods()) {
-      genericTypes.addAll(factoryMethod.getGenericTypes());
-    }
+    Set<GenericType> genericTypes = beanReader.allGenericTypes();
     if (!genericTypes.isEmpty()) {
       for (GenericType type : genericTypes) {
         writer.append("  public static final Type TYPE_%s = new GenericType<", type.shortName());
         type.writeShort(writer);
-        writer.append(">(){};").eol();
+        writer.append(">(){}.type();").eol();
       }
       writer.eol();
     }
@@ -125,7 +102,7 @@ class SimpleBeanWriter {
     if (method.isProtoType()) {
       method.builderAddProtoBean(writer);
     } else {
-      writer.append(method.builderBuildBean()).eol();
+      method.builderBuildBean(writer);
       method.builderBuildAddBean(writer);
       writer.append("    }").eol();
     }
@@ -138,7 +115,7 @@ class SimpleBeanWriter {
       context.logError(beanReader.getBeanType(), "Unable to determine constructor to use for %s? Add explicit @Inject to one of the constructors.", beanReader.getBeanType());
       return;
     }
-    writeBuildMethodStart(constructor);
+    writeBuildMethodStart();
     if (proxied) {
       writer.append("    // this bean is proxied, see %s$Proxy$DI instead", shortName).eol();
     } else {
@@ -168,27 +145,13 @@ class SimpleBeanWriter {
     writer.append("    }").eol();
   }
 
-  private void writeBuildMethodStart(MethodReader constructor) {
-    int providerIndex = 0;
+  private void writeBuildMethodStart() {
     if (beanReader.prototype()) {
       writer.append(CODE_COMMENT_BUILD_PROVIDER, shortName).eol();
     } else {
       writer.append(CODE_COMMENT_BUILD, shortName).eol();
     }
-    writer.append("  public static void build(%s builder", beanReader.builderType());
-    for (MethodReader.MethodParam param : constructor.getParams()) {
-      if (param.isGenericParam()) {
-        param.addProviderParam(writer, providerIndex++);
-      }
-    }
-    for (MethodReader methodReader : beanReader.getInjectMethods()) {
-      for (MethodReader.MethodParam param : methodReader.getParams()) {
-        if (param.isGenericParam()) {
-          param.addProviderParam(writer, providerIndex++);
-        }
-      }
-    }
-    writer.append(") {").eol();
+    writer.append("  public static void build(%s builder) {", beanReader.builderType()).eol();
   }
 
   String indent = "     ";
@@ -235,7 +198,7 @@ class SimpleBeanWriter {
       if (i > 0) {
         writer.append(", ");
       }
-      writer.append(methodParams.get(i).builderGetDependency(builderRef, false));
+      methodParams.get(i).builderGetDependency(writer, builderRef, false);
     }
     writer.append(");").eol();
   }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java
index 8f8b7b85..56400333 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java
@@ -17,32 +17,40 @@ class TypeAppender {
     this.importTypes = importTypes;
   }
 
-  void add(String classType) {
-    importTypes.add(classType);
-    types.add(Util.shortName(classType) + ".class");
+  void add(GenericType type) {
+    if (type.isGenericType()) {
+      addGenericType(type);
+    } else {
+      addSimpleType(type.topType());
+    }
   }
 
-  void add(List<String> types) {
-    for (String type : types) {
-      if (GenericType.isGeneric(type)) {
-        genericTypes.add(GenericType.parse(type));
+  void add(List<String> sourceTypes) {
+    for (String type : sourceTypes) {
+      GenericType genericType = GenericType.parse(type);
+      if (genericType.isGenericType()) {
+        addGenericType(genericType);
       } else {
-        add(type);
+        addSimpleType(genericType.topType());
       }
     }
   }
 
+  void addSimpleType(String classType) {
+    importTypes.add(classType);
+    types.add(Util.shortName(classType) + ".class");
+  }
+
+  private void addGenericType(GenericType genericType) {
+    genericTypes.add(genericType);
+    types.add("TYPE_" + genericType.shortName());
+  }
+
   Set<GenericType> genericTypes() {
     return genericTypes;
   }
 
   String asString() {
-    if (!genericTypes.isEmpty()) {
-      for (GenericType genericType : genericTypes) {
-        types.add("TYPE_" + genericType.shortName());
-      }
-    }
-
     int count = 0;
     final StringBuilder sb = new StringBuilder();
     for (String type : types) {
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 b1b19106..fa2d20ed 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
@@ -156,6 +156,19 @@ class TypeExtendsInjection {
     return aspectMethods.isEmpty() ? BeanAspects.EMPTY : new BeanAspects(aspectMethods);
   }
 
+  void removeFromProvides(List<String> provides) {
+    MethodReader constructor = getConstructor();
+    if (constructor != null) {
+      constructor.removeFromProvides(provides);
+    }
+    for (FieldReader injectField : injectFields) {
+      injectField.removeFromProvides(provides);
+    }
+    for (MethodReader injectMethod : injectMethods.values()) {
+      injectMethod.removeFromProvides(provides);
+    }
+  }
+
   List<FieldReader> getInjectFields() {
     List<FieldReader> list = new ArrayList<>(injectFields);
     Collections.reverse(list);
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java
index 02d97120..ae5737ff 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java
@@ -13,32 +13,32 @@ import java.util.List;
 class TypeExtendsReader {
 
   private static final String JAVA_LANG_OBJECT = "java.lang.Object";
+  private final GenericType baseGenericType;
   private final TypeElement baseType;
   private final ProcessingContext context;
-  private final List<String> extendsTypes = new ArrayList<>();
   private final TypeExtendsInjection extendsInjection;
+  private final List<String> extendsTypes = new ArrayList<>();
   private final List<String> interfaceTypes = new ArrayList<>();
+  private final List<String> providesTypes = new ArrayList<>();
   private final String beanSimpleName;
+  private final String baseTypeRaw;
   private boolean closeable;
   /**
    * The implied qualifier name based on naming convention.
    */
   private String qualifierName;
-  private String baseTypeRaw;
 
-  TypeExtendsReader(TypeElement baseType, ProcessingContext context, boolean factory) {
+  TypeExtendsReader(GenericType baseGenericType, TypeElement baseType, ProcessingContext context, boolean factory) {
+    this.baseGenericType = baseGenericType;
     this.baseType = baseType;
     this.context = context;
     this.extendsInjection = new TypeExtendsInjection(baseType, context, factory);
     this.beanSimpleName = baseType.getSimpleName().toString();
+    this.baseTypeRaw = Util.unwrapProvider(baseGenericType.toString());
   }
 
-  String getBaseType() {
-    return baseTypeRaw;
-  }
-
-  List<String> getExtendsTypes() {
-    return extendsTypes;
+  GenericType getBaseType() {
+    return baseGenericType;
   }
 
   String getQualifierName() {
@@ -73,16 +73,8 @@ class TypeExtendsReader {
     return extendsInjection.getConstructor();
   }
 
-  List<String> getInterfaceTypes() {
-    return interfaceTypes;
-  }
-
   List<String> getProvides() {
-    List<String> all = new ArrayList<>(extendsTypes.size() + interfaceTypes.size());
-    all.addAll(extendsTypes);
-    all.addAll(interfaceTypes);
-    all.remove(baseTypeRaw);
-    return all;
+    return providesTypes;
   }
 
   boolean isCloseable() {
@@ -90,13 +82,9 @@ class TypeExtendsReader {
   }
 
   void process(boolean forBean) {
-    String base = Util.unwrapProvider(baseType.getQualifiedName().toString());
-    if (!GenericType.isGeneric(base)) {
-      baseTypeRaw = base;
-      extendsTypes.add(base);
-      if (forBean) {
-        extendsInjection.read(baseType);
-      }
+    extendsTypes.add(baseTypeRaw);
+    if (forBean) {
+      extendsInjection.read(baseType);
     }
     readInterfaces(baseType);
     TypeElement superElement = superOf(baseType);
@@ -110,6 +98,12 @@ class TypeExtendsReader {
       }
       addSuperType(superElement);
     }
+
+    providesTypes.addAll(extendsTypes);
+    providesTypes.addAll(interfaceTypes);
+    providesTypes.remove(baseTypeRaw);
+    // we can't provide a type that is getting injected
+    extendsInjection.removeFromProvides(providesTypes);
   }
 
   private void addSuperType(TypeElement element) {
@@ -117,13 +111,11 @@ class TypeExtendsReader {
     String fullName = element.getQualifiedName().toString();
     if (!fullName.equals(JAVA_LANG_OBJECT)) {
       String type = Util.unwrapProvider(fullName);
-      if (!GenericType.isGeneric(type)) {
-        if (isPublic(element)) {
-          extendsTypes.add(type);
-          extendsInjection.read(element);
-        }
-        addSuperType(superOf(element));
+      if (isPublic(element)) {
+        extendsTypes.add(type);
+        extendsInjection.read(element);
       }
+      addSuperType(superOf(element));
     }
   }
 
@@ -133,38 +125,38 @@ class TypeExtendsReader {
 
   private void readInterfaces(TypeElement type) {
     for (TypeMirror anInterface : type.getInterfaces()) {
-      Element element = context.asElement(anInterface);
-      if (isPublic(element)) {
-        String rawType = Util.unwrapProvider(anInterface.toString());
-        if (rawType.indexOf('.') == -1) {
-          context.logWarn("skip when no package on interface " + rawType);
-        } else if (Constants.AUTO_CLOSEABLE.equals(rawType) || Constants.IO_CLOSEABLE.equals(rawType)) {
-          closeable = true;
-        } else {
-          rawType = GenericType.removeParameter(rawType);
-          if (qualifierName == null) {
-            final String iShortName = Util.shortName(rawType);
-            if (beanSimpleName.endsWith(iShortName)) {
-              // derived qualifier name based on prefix to interface short name
-              qualifierName = beanSimpleName.substring(0, beanSimpleName.length() - iShortName.length()).toLowerCase();
-            }
-          }
-          interfaceTypes.add(rawType);
-          readExtendedInterfaces(rawType);
+      if (isPublic(context.asElement(anInterface))) {
+        readInterfacesOf(anInterface);
+      }
+    }
+  }
+
+  private void readInterfacesOf(TypeMirror anInterface) {
+    String rawType = Util.unwrapProvider(anInterface.toString());
+    if (JAVA_LANG_OBJECT.equals(rawType)) {
+      return;
+    }
+    if (rawType.indexOf('.') == -1) {
+      context.logWarn("skip when no package on interface " + rawType);
+    } else if (Constants.AUTO_CLOSEABLE.equals(rawType) || Constants.IO_CLOSEABLE.equals(rawType)) {
+      closeable = true;
+    } else {
+      if (qualifierName == null) {
+        String mainType = GenericType.removeParameter(rawType);
+        final String iShortName = Util.shortName(mainType);
+        if (beanSimpleName.endsWith(iShortName)) {
+          // derived qualifier name based on prefix to interface short name
+          qualifierName = beanSimpleName.substring(0, beanSimpleName.length() - iShortName.length()).toLowerCase();
         }
       }
+      interfaceTypes.add(rawType);
+      for (TypeMirror supertype : context.types().directSupertypes(anInterface)) {
+        readInterfacesOf(supertype);
+      }
     }
   }
 
   private boolean isPublic(Element element) {
     return element != null && element.getModifiers().contains(Modifier.PUBLIC);
   }
-
-  private void readExtendedInterfaces(String type) {
-    GenericType genericType = GenericType.parse(type);
-    final TypeElement element = context.element(genericType.topType());
-    if (element != null) {
-      readInterfaces(element);
-    }
-  }
 }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java
index d6acfd5e..e2b9622a 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java
@@ -18,19 +18,19 @@ class TypeReader {
   private Set<GenericType> genericTypes;
   private String typesRegister;
 
-  TypeReader(TypeElement beanType, ProcessingContext context, Set<String> importTypes, boolean factory) {
-    this(true, beanType, context, importTypes, factory);
+  TypeReader(GenericType genericType, TypeElement beanType, ProcessingContext context, Set<String> importTypes, boolean factory) {
+    this(genericType, true, beanType, context, importTypes, factory);
   }
 
-  TypeReader(TypeElement returnElement, ProcessingContext context) {
-    this(false, returnElement, context, new LinkedHashSet<>(), false);
+  TypeReader(GenericType genericType, TypeElement returnElement, ProcessingContext context) {
+    this(genericType, false, returnElement, context, new LinkedHashSet<>(), false);
   }
 
-  private TypeReader(boolean forBean, TypeElement beanType, ProcessingContext context, Set<String> importTypes, boolean factory) {
+  private TypeReader(GenericType genericType, boolean forBean, TypeElement beanType, ProcessingContext context, Set<String> importTypes, boolean factory) {
     this.forBean = forBean;
     this.beanType = beanType;
     this.importTypes = importTypes;
-    this.extendsReader = new TypeExtendsReader(beanType, context, factory);
+    this.extendsReader = new TypeExtendsReader(genericType, beanType, context, factory);
     this.annotationReader = new TypeAnnotationReader(beanType, context);
   }
 
@@ -104,8 +104,7 @@ class TypeReader {
   private void initRegistrationTypes() {
     TypeAppender appender = new TypeAppender(importTypes);
     appender.add(extendsReader.getBaseType());
-    appender.add(extendsReader.getExtendsTypes());
-    appender.add(extendsReader.getInterfaceTypes());
+    appender.add(extendsReader.getProvides());
     if (forBean) {
       appender.add(annotationReader.getAnnotationTypes());
     }
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 594966f5..6282737a 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
@@ -9,7 +9,7 @@ import javax.lang.model.type.TypeMirror;
 
 class Util {
 
-  private static final String PROVIDER_PREFIX = "jakarta.inject.Provider<";
+  static final String PROVIDER_PREFIX = "jakarta.inject.Provider<";
   private static final String OPTIONAL_PREFIX = "java.util.Optional<";
   private static final String NULLABLE = "Nullable";
   private static final int PROVIDER_LENGTH = PROVIDER_PREFIX.length();
@@ -101,6 +101,10 @@ class Util {
     }
   }
 
+  static String trimmedName(GenericType type) {
+    return shortName(type.topType()).toLowerCase();
+  }
+
   static boolean isOptional(String rawType) {
     return rawType.startsWith(OPTIONAL_PREFIX);
   }
diff --git a/inject-generator/src/main/resources/META-INF/gradle/incremental.annotation.processors b/inject-generator/src/main/resources/META-INF/gradle/incremental.annotation.processors
new file mode 100644
index 00000000..71ec82ce
--- /dev/null
+++ b/inject-generator/src/main/resources/META-INF/gradle/incremental.annotation.processors
@@ -0,0 +1,1 @@
+io.avaje.inject.generator.Processor,aggregating
diff --git a/inject/pom.xml b/inject/pom.xml
index d64a3629..1c294cd2 100644
--- a/inject/pom.xml
+++ b/inject/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>io.avaje</groupId>
     <artifactId>avaje-inject-parent</artifactId>
-    <version>8.5</version>
+    <version>8.6</version>
   </parent>
 
   <artifactId>avaje-inject</artifactId>
diff --git a/inject/src/main/java/io/avaje/inject/BeanScope.java b/inject/src/main/java/io/avaje/inject/BeanScope.java
index 361070a8..c222a052 100644
--- a/inject/src/main/java/io/avaje/inject/BeanScope.java
+++ b/inject/src/main/java/io/avaje/inject/BeanScope.java
@@ -201,6 +201,11 @@ public interface BeanScope extends AutoCloseable {
    */
   <T> List<T> list(Class<T> interfaceType);
 
+  /**
+   * Return the list of beans that implement the given type.
+   */
+  <T> List<T> list(Type interfaceType);
+
   /**
    * Return the list of beans that implement the interface sorting by priority.
    */
diff --git a/inject/src/main/java/io/avaje/inject/spi/Builder.java b/inject/src/main/java/io/avaje/inject/spi/Builder.java
index 0622f860..483c2889 100644
--- a/inject/src/main/java/io/avaje/inject/spi/Builder.java
+++ b/inject/src/main/java/io/avaje/inject/spi/Builder.java
@@ -100,32 +100,32 @@ public interface Builder {
   /**
    * Get an optional dependency.
    */
-  <T> Optional<T> getOptional(Class<T> cls);
+  <T> Optional<T> getOptional(Type cls);
 
   /**
    * Get an optional named dependency.
    */
-  <T> Optional<T> getOptional(Class<T> cls, String name);
+  <T> Optional<T> getOptional(Type cls, String name);
 
   /**
    * Get an optional dependency potentially returning null.
    */
-  <T> T getNullable(Class<T> cls);
+  <T> T getNullable(Type cls);
 
   /**
    * Get an optional named dependency potentially returning null.
    */
-  <T> T getNullable(Class<T> cls, String name);
+  <T> T getNullable(Type cls, String name);
 
   /**
    * Return Provider of T given the type.
    */
-  <T> Provider<T> getProvider(Class<T> cls);
+  <T> Provider<T> getProvider(Type cls);
 
   /**
    * Return Provider of T given the type and name.
    */
-  <T> Provider<T> getProvider(Class<T> cls, String name);
+  <T> Provider<T> getProvider(Type cls, String name);
 
   /**
    * Return Provider for a generic interface type.
@@ -138,22 +138,22 @@ public interface Builder {
   /**
    * Get a dependency.
    */
-  <T> T get(Class<T> cls);
+  <T> T get(Type cls);
 
   /**
    * Get a named dependency.
    */
-  <T> T get(Class<T> cls, String name);
+  <T> T get(Type cls, String name);
 
   /**
    * Get a list of dependencies for the interface type .
    */
-  <T> List<T> list(Class<T> interfaceType);
+  <T> List<T> list(Type interfaceType);
 
   /**
    * Get a set of dependencies for the interface type .
    */
-  <T> Set<T> set(Class<T> interfaceType);
+  <T> Set<T> set(Type interfaceType);
 
   /**
    * Build and return the bean scope.
diff --git a/inject/src/main/java/io/avaje/inject/spi/DBeanMap.java b/inject/src/main/java/io/avaje/inject/spi/DBeanMap.java
index 1a49cab6..a223eff3 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DBeanMap.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DBeanMap.java
@@ -124,17 +124,29 @@ class DBeanMap {
   boolean isSupplied(String qualifierName, Type... types) {
     if (types != null) {
       for (Type type : types) {
-        if (isSuppliedType(qualifierName, type)) {
-          return true;
+        DContextEntry entry = beans.get(type.getTypeName());
+        if (entry != null) {
+          DContextEntryBean suppliedBean = entry.supplied(qualifierName);
+          if (suppliedBean != null && types.length > 1) {
+            addSuppliedFor(type, types, suppliedBean);
+            return true;
+          }
         }
       }
     }
     return false;
   }
 
-  private boolean isSuppliedType(String qualifierName, Type type) {
-    DContextEntry entry = beans.get(type.getTypeName());
-    return entry != null && entry.isSupplied(qualifierName);
+  /**
+   * Register the suppliedBean entry with other types (like other interfaces)
+   * IF those types don't already have a registered entry.
+   */
+  private void addSuppliedFor(Type matchType, Type[] types, DContextEntryBean suppliedBean) {
+    for (Type type : types) {
+      if (type != matchType) {
+        beans.computeIfAbsent(type.getTypeName(), s -> new DContextEntry()).add(suppliedBean);
+      }
+    }
   }
 
   /**
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 6904e9c7..bde6963a 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DBeanScope.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DBeanScope.java
@@ -101,11 +101,9 @@ class DBeanScope implements BeanScope {
   @Nullable
   Object getStrict(String name, Type[] types) {
     for (Type type : types) {
-      if (!isAnnotationType(type)) {
-        Object match = beans.getStrict(type, name);
-        if (match != null) {
-          return match;
-        }
+      Object match = beans.getStrict(type, name);
+      if (match != null) {
+        return match;
       }
     }
     if (parent instanceof DBeanScope) {
@@ -115,10 +113,6 @@ class DBeanScope implements BeanScope {
     return null;
   }
 
-  private boolean isAnnotationType(Type type) {
-    return type instanceof Class && ((Class<?>) type).isAnnotation();
-  }
-
   @Override
   public <T> Optional<T> getOptional(Class<T> type) {
     return getMaybe(type, null);
@@ -140,9 +134,18 @@ class DBeanScope implements BeanScope {
     return parent.getOptional(type, name);
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   public <T> List<T> list(Class<T> interfaceType) {
+    return listOf(interfaceType);
+  }
+
+  @Override
+  public <T> List<T> list(Type interfaceType) {
+    return listOf(interfaceType);
+  }
+
+  @SuppressWarnings("unchecked")
+  private  <T> List<T> listOf(Type interfaceType) {
     List<T> values = (List<T>) beans.all(interfaceType);
     if (parent == null) {
       return values;
diff --git a/inject/src/main/java/io/avaje/inject/spi/DBuilder.java b/inject/src/main/java/io/avaje/inject/spi/DBuilder.java
index f72b3e98..01744bc3 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DBuilder.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DBuilder.java
@@ -66,12 +66,32 @@ class DBuilder implements Builder {
     if (parent instanceof DBeanScope) {
       // effectively looking for a match in the test scope
       DBeanScope dParent = (DBeanScope) parent;
-      parentMatch = dParent.getStrict(name, types);
+      parentMatch = dParent.getStrict(name, removeAnnotations(types));
       return parentMatch == null;
     }
     return true;
   }
 
+  /**
+   * Return the types without any annotation types.
+   * <p>
+   * For the purposes of supplied beans (typically test doubles) we are not
+   * interested in annotation types.
+   */
+  protected Type[] removeAnnotations(Type[] source) {
+    for (int i = 1, end = source.length; i < end; i++) {
+      if (isAnnotationType(source[i])) {
+        // the annotation types are always at the tail so just return leading types
+        return Arrays.copyOf(source, i);
+      }
+    }
+    return source;
+  }
+
+  private boolean isAnnotationType(Type type) {
+    return type instanceof Class && ((Class<?>) type).isAnnotation();
+  }
+
   protected void next(String name, Type... types) {
     injectTarget = firstOf(types);
     beanMap.nextBean(name, types);
@@ -82,13 +102,13 @@ class DBuilder implements Builder {
   }
 
   @Override
-  public <T> Set<T> set(Class<T> interfaceType) {
+  public <T> Set<T> set(Type interfaceType) {
     return new LinkedHashSet<>(list(interfaceType));
   }
 
   @SuppressWarnings({"unchecked"})
   @Override
-  public <T> List<T> list(Class<T> interfaceType) {
+  public <T> List<T> list(Type interfaceType) {
     List<T> values = (List<T>) beanMap.all(interfaceType);
     if (parent == null) {
       return values;
@@ -161,33 +181,33 @@ class DBuilder implements Builder {
   }
 
   @Override
-  public <T> Optional<T> getOptional(Class<T> cls) {
+  public <T> Optional<T> getOptional(Type cls) {
     return getOptional(cls, null);
   }
 
   @Override
-  public <T> Optional<T> getOptional(Class<T> cls, String name) {
+  public <T> Optional<T> getOptional(Type cls, String name) {
     T bean = getMaybe(cls, name);
     return Optional.ofNullable(bean);
   }
 
   @Override
-  public <T> T getNullable(Class<T> cls) {
+  public <T> T getNullable(Type cls) {
     return getNullable(cls, null);
   }
 
   @Override
-  public <T> T getNullable(Class<T> cls, String name) {
+  public <T> T getNullable(Type cls, String name) {
     return getMaybe(cls, name);
   }
 
   @Override
-  public <T> Provider<T> getProvider(Class<T> cls) {
+  public <T> Provider<T> getProvider(Type cls) {
     return getProvider(cls, null);
   }
 
   @Override
-  public <T> Provider<T> getProvider(Class<T> cls, String name) {
+  public <T> Provider<T> getProvider(Type cls, String name) {
     if (runningPostConstruct) {
       return obtainProvider(cls, name);
     }
@@ -197,7 +217,7 @@ class DBuilder implements Builder {
     return promise;
   }
 
-  <T> Provider<T> obtainProvider(Class<T> type, String name) {
+  <T> Provider<T> obtainProvider(Type type, String name) {
     Provider<T> provider = beanMap.provider(type, name);
     if (provider != null) {
       return provider;
@@ -221,12 +241,12 @@ class DBuilder implements Builder {
   }
 
   @Override
-  public <T> T get(Class<T> cls) {
+  public <T> T get(Type cls) {
     return get(cls, null);
   }
 
   @Override
-  public <T> T get(Class<T> cls, String name) {
+  public <T> T get(Type cls, String name) {
     T bean = getMaybe(cls, name);
     if (bean == null) {
       throw new IllegalStateException(errorInjectingNull(cls, name));
@@ -234,8 +254,8 @@ class DBuilder implements Builder {
     return bean;
   }
 
-  private <T> String errorInjectingNull(Class<T> cls, String name) {
-    String msg = "Injecting null for " + cls.getName();
+  private <T> String errorInjectingNull(Type cls, String name) {
+    String msg = "Injecting null for " + cls.getTypeName();
     if (name != null) {
       msg += " name:" + name;
     }
diff --git a/inject/src/main/java/io/avaje/inject/spi/DBuilderExtn.java b/inject/src/main/java/io/avaje/inject/spi/DBuilderExtn.java
index c71a70cb..1a77008a 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DBuilderExtn.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DBuilderExtn.java
@@ -39,7 +39,7 @@ class DBuilderExtn extends DBuilder {
       return false;
     }
     if (hasSuppliedBeans) {
-      return !beanMap.isSupplied(qualifierName, types);
+      return !beanMap.isSupplied(qualifierName, removeAnnotations(types));
     }
     return true;
   }
diff --git a/inject/src/main/java/io/avaje/inject/spi/DContextEntry.java b/inject/src/main/java/io/avaje/inject/spi/DContextEntry.java
index 9e83583d..44515c01 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DContextEntry.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DContextEntry.java
@@ -63,15 +63,15 @@ class DContextEntry {
   }
 
   /**
-   * Return true if a supplied bean is one of the entries.
+   * Return a supplied bean is one of the entries.
    */
-  boolean isSupplied(String qualifierName) {
+  DContextEntryBean supplied(String qualifierName) {
     for (DContextEntryBean entry : entries) {
       if (entry.isSupplied(qualifierName)) {
-        return true;
+        return entry;
       }
     }
-    return false;
+    return null;
   }
 
   static final class EntryMatcher {
diff --git a/inject/src/main/java/io/avaje/inject/spi/GenericType.java b/inject/src/main/java/io/avaje/inject/spi/GenericType.java
index 00d2bb1f..7c0409fe 100644
--- a/inject/src/main/java/io/avaje/inject/spi/GenericType.java
+++ b/inject/src/main/java/io/avaje/inject/spi/GenericType.java
@@ -29,7 +29,7 @@ public class GenericType<T> implements Type {
 
   @Override
   public String getTypeName() {
-    return type.toString();
+    return type.getTypeName();
   }
 
   @Override
diff --git a/inject/src/main/java/io/avaje/inject/spi/ProviderPromise.java b/inject/src/main/java/io/avaje/inject/spi/ProviderPromise.java
index 8a7ba479..3ae823bf 100644
--- a/inject/src/main/java/io/avaje/inject/spi/ProviderPromise.java
+++ b/inject/src/main/java/io/avaje/inject/spi/ProviderPromise.java
@@ -1,6 +1,8 @@
 package io.avaje.inject.spi;
 
 import jakarta.inject.Provider;
+
+import java.lang.reflect.Type;
 import java.util.function.Consumer;
 
 /**
@@ -8,12 +10,12 @@ import java.util.function.Consumer;
  */
 final class ProviderPromise<T> implements Provider<T>, Consumer<Builder> {
 
-  private final Class<T> type;
+  private final Type type;
   private final String name;
   private final DBuilder builder;
   private Provider<T> provider;
 
-  ProviderPromise(Class<T> type, String name, DBuilder builder) {
+  ProviderPromise(Type type, String name, DBuilder builder) {
     this.type = type;
     this.name = name;
     this.builder = builder;
diff --git a/pom.xml b/pom.xml
index 74cb7458..8049bde8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
 
   <groupId>io.avaje</groupId>
   <artifactId>avaje-inject-parent</artifactId>
-  <version>8.5</version>
+  <version>8.6</version>
   <packaging>pom</packaging>
   <name>avaje inject parent</name>
   <description>parent pom for avaje inject library</description>


Test patch:
diff --git a/blackbox-test-inject/pom.xml b/blackbox-test-inject/pom.xml
index 2a2f0e6a..e48c6147 100644
--- a/blackbox-test-inject/pom.xml
+++ b/blackbox-test-inject/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <artifactId>avaje-inject-parent</artifactId>
     <groupId>io.avaje</groupId>
-    <version>8.5</version>
+    <version>8.6</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/GenericTypeTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/GenericTypeTest.java
index 9d469d5d..a7de34df 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/GenericTypeTest.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/GenericTypeTest.java
@@ -12,18 +12,33 @@ import static org.junit.jupiter.api.Assertions.*;
 
 class GenericTypeTest {
 
+  @Test
+  void equals() {
+    GenericType t0 = GenericType.parse("java.lang.List");
+    GenericType t1 = GenericType.parse("java.lang.List");
+    assertThat(t0).isEqualTo(t1);
+
+    GenericType t2 = GenericType.parse("java.lang.List<?>");
+    assertThat(t0).isEqualTo(t2);
+    GenericType t3 = GenericType.parse("java.lang.List<T>");
+    assertThat(t0).isEqualTo(t3);
+
+    Set<GenericType> set = new LinkedHashSet<>();
+    set.add(t0);
+    set.add(t1);
+    set.add(t2);
+    set.add(t3);
+    assertThat(set).hasSize(1);
+  }
+
   @Test
   void isGeneric() {
     assertFalse(GenericType.isGeneric("java.lang.List"));
     assertTrue(GenericType.isGeneric("java.lang.List<Foo>"));
     assertTrue(GenericType.isGeneric("java.lang.List<?>"));
+    assertTrue(GenericType.isGeneric("java.lang.List<org.Foo>"));
   }
 
-  @Test
-  void maybe() {
-    assertThat(GenericType.maybe("java.lang.List<Foo>").getMainType()).isEqualTo("java.lang.List");
-    assertThat(GenericType.maybe("java.lang.List")).isNull();
-  }
 
   @Test
   void parse() {
@@ -42,6 +57,8 @@ class GenericTypeTest {
     assertThat(type.topType()).isEqualTo("my.exa.Repo");
     assertThat(type.getMainType()).isNull();
     assertThat(type.getParams()).isEmpty();
+    assertThat(type.isGenericType()).isFalse();
+    assertThat(type.isProviderType()).isFalse();
 
     Set<String> importSet = new HashSet<>();
     type.addImports(importSet);
@@ -49,13 +66,35 @@ class GenericTypeTest {
     assertThat(importSet).containsOnly("my.exa.Repo");
   }
 
+  @Test
+  void parse_provider() {
+    GenericType type = GenericType.parse(Util.PROVIDER_PREFIX + "my.exa.Repo" + ">");
+    assertThat(type.isGenericType()).isTrue();
+    assertThat(type.isProviderType()).isTrue();
+    assertThat(type.shortName()).isEqualTo("ProviderRepo");
+  }
+
   @Test
   void parse_genericWildcard() {
-    GenericType type = GenericType.parse("my.exa.Repo<?>");
+    assertsForRepo(GenericType.parse("my.exa.Repo<?>"));
+  }
+
+  @Test
+  void parse_genericTypeParam() {
+    assertsForRepo(GenericType.parse("my.exa.Repo<Something>"));
+  }
+
+  @Test
+  void parse_genericTypeParams() {
+    assertsForRepo(GenericType.parse("my.exa.Repo<Something,T,X>"));
+  }
+
+  private void assertsForRepo(GenericType type) {
     assertThat(type.shortName()).isEqualTo("Repo");
     assertThat(type.topType()).isEqualTo("my.exa.Repo");
     assertThat(type.getMainType()).isNull();
     assertThat(type.getParams()).isEmpty();
+    assertThat(type.isGenericType()).isFalse();
 
     Set<String> importSet = new HashSet<>();
     type.addImports(importSet);
@@ -69,6 +108,7 @@ class GenericTypeTest {
     assertThat(type.shortName()).isEqualTo("RepoOther");
     assertThat(type.topType()).isEqualTo("my.exa.Repo");
     assertThat(type.getMainType()).isEqualTo("my.exa.Repo");
+    assertThat(type.isGenericType()).isTrue();
     assertThat(type.getParams()).hasSize(1);
     assertThat(type.getParams().get(0).getMainType()).isEqualTo("my.Other");
 
@@ -83,6 +123,7 @@ class GenericTypeTest {
     GenericType type = GenericType.parse("my.exa.Repo<my.d.Haz,java.lang.Long>");
 
     assertThat(type.getMainType()).isEqualTo("my.exa.Repo");
+    assertThat(type.isGenericType()).isTrue();
     assertThat(type.getParams()).hasSize(2);
     assertThat(type.getParams().get(0).getMainType()).isEqualTo("my.d.Haz");
     assertThat(type.getParams().get(1).getMainType()).isEqualTo("java.lang.Long");
@@ -93,6 +134,7 @@ class GenericTypeTest {
     GenericType type = GenericType.parse("java.util.concurrent.ConcurrentMap<java.lang.String,my.d.Has>");
 
     assertThat(type.getMainType()).isEqualTo("java.util.concurrent.ConcurrentMap");
+    assertThat(type.isGenericType()).isTrue();
     assertThat(type.getParams()).hasSize(2);
     assertThat(type.getParams().get(0).getMainType()).isEqualTo("java.lang.String");
     assertThat(type.getParams().get(1).getMainType()).isEqualTo("my.d.Has");
@@ -109,6 +151,7 @@ class GenericTypeTest {
     GenericType type = new GenericTypeParser("my.exa.Repo<? extends my.d.Haz,java.lang.Long>").parse();
 
     assertThat(type.getMainType()).isEqualTo("my.exa.Repo");
+    assertThat(type.isGenericType()).isTrue();
     assertThat(type.getParams()).hasSize(2);
     assertThat(type.getParams().get(0).getMainType()).isEqualTo("? extends my.d.Haz");
     assertThat(type.getParams().get(1).getMainType()).isEqualTo("java.lang.Long");
@@ -119,6 +162,7 @@ class GenericTypeTest {
     GenericType type = new GenericTypeParser("my.exa.Repo<my.a.Prov<my.b.Haz>,my.a.Key<java.util.UUID>>").parse();
 
     assertThat(type.getMainType()).isEqualTo("my.exa.Repo");
+    assertThat(type.isGenericType()).isTrue();
     assertThat(type.getParams()).hasSize(2);
     assertThat(type.getParams().get(0).getMainType()).isEqualTo("my.a.Prov");
     assertThat(type.getParams().get(0).getParams()).hasSize(1);
@@ -131,16 +175,12 @@ class GenericTypeTest {
 
   @Test
   void shortName() {
-    assertThat(GenericType.parse("java.lang.List<Foo>").shortName()).isEqualTo("ListFoo");
+    assertThat(GenericType.parse("java.lang.List<T>").shortName()).isEqualTo("List");
+    assertThat(GenericType.parse("java.lang.List<Foo>").shortName()).isEqualTo("List");
+    assertThat(GenericType.parse("java.lang.List<FOO>").shortName()).isEqualTo("List");
     assertThat(GenericType.parse("java.lang.List<org.Foo<com.Bar>>").shortName()).isEqualTo("ListFooBar");
   }
 
-  @Test
-  void hasParameter() {
-    assertThat(GenericType.parse("java.lang.List<T>").hasParameter()).isTrue();
-    assertThat(GenericType.parse("java.lang.List<org.Foo<TYPE>").hasParameter()).isTrue();
-    assertThat(GenericType.parse("java.lang.List<org.Foo<com.Bar>>").hasParameter()).isFalse();
-  }
 
   @Test
   void removeParameter() {
@@ -148,7 +188,7 @@ class GenericTypeTest {
     assertThat(GenericType.removeParameter("java.lang.List<org.Foo<T>>")).isEqualTo("java.lang.List");
     assertThat(GenericType.removeParameter("java.lang.List<org.Foo<TYPE>>")).isEqualTo("java.lang.List");
     assertThat(GenericType.removeParameter("face.FaceParam<T>")).isEqualTo("face.FaceParam");
-    assertThat(GenericType.removeParameter("java.lang.List<org.Foo<com.Bar>>")).isEqualTo("java.lang.List<org.Foo<com.Bar>>");
+    assertThat(GenericType.removeParameter("java.lang.List<org.Foo<com.Bar>>")).isEqualTo("java.lang.List");
   }
 
   @Test
@@ -158,6 +198,16 @@ class GenericTypeTest {
     assertEquals("my.exa.Repo<my.Other>", GenericType.trimWildcard("my.exa.Repo<my.Other>"));
   }
 
+  @Test
+  void trimGenericParams() {
+    assertEquals("my.exa.Repo", GenericType.trimGenericParams("my.exa.Repo<?>"));
+    assertEquals("my.exa.Repo", GenericType.trimWildcard("my.exa.Repo<T>"));
+    assertEquals("my.exa.Repo", GenericType.trimWildcard("my.exa.Repo<A,B>"));
+    assertEquals("my.exa.Repo", GenericType.trimWildcard("my.exa.Repo<MyParam,OtherParam,More>"));
+    assertEquals("my.exa.Repo<my.Other>", GenericType.trimWildcard("my.exa.Repo<my.Other>"));
+    assertEquals("my.exa.Repo<unexpected", GenericType.trimWildcard("my.exa.Repo<unexpected"));
+  }
+
   @Test
   void write() {
     GenericType type = new GenericTypeParser("my.exa.Repo<my.a.Prov<my.b.Haz>,my.a.Key<java.util.UUID>>").parse();
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
index 9fe68f51..0d4e01e9 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/UtilTest.java
@@ -34,6 +34,11 @@ class UtilTest {
     assertEquals(Util.commonParent("org.foo.web.foo", "org.foo.service.blah"), "org.foo");
   }
 
+  @Test
+  void unwrapProvider() {
+    assertEquals(Util.unwrapProvider("jakarta.inject.Provider<org.Foo<com.Bazz>>"), "org.Foo<com.Bazz>");
+  }
+
   @Test
   void extractList() {
     assertEquals("Foo", Util.extractList("List<? extends Foo>"));
diff --git a/inject-test/pom.xml b/inject-test/pom.xml
index 040f675e..b2260aed 100644
--- a/inject-test/pom.xml
+++ b/inject-test/pom.xml
@@ -4,7 +4,7 @@
   <parent>
     <groupId>io.avaje</groupId>
     <artifactId>avaje-inject-parent</artifactId>
-    <version>8.5</version>
+    <version>8.6</version>
   </parent>
 
   <artifactId>avaje-inject-test</artifactId>
@@ -124,7 +124,7 @@
             <path>
               <groupId>io.avaje</groupId>
               <artifactId>avaje-inject-generator</artifactId>
-              <version>8.5</version>
+              <version>8.6</version>
             </path>
           </annotationProcessorPaths>
         </configuration>
diff --git a/inject-test/src/test/java/org/example/autonamed/MyAutoNameTest.java b/inject-test/src/test/java/org/example/autonamed/MyAutoNameTest.java
index 0dec1988..f38e4586 100644
--- a/inject-test/src/test/java/org/example/autonamed/MyAutoNameTest.java
+++ b/inject-test/src/test/java/org/example/autonamed/MyAutoNameTest.java
@@ -40,8 +40,8 @@ class MyAutoNameTest {
       assertThat(myAutoName2.who()).isEqualTo("one");
 
       final MyGeneric<?> genericWild = beanScope.get(MyGeneric.class, "wild");
-      final MyGeneric<?> generic2 = beanScope.get(MyGeneric.class, "genericString");
-      final MyGeneric<?> generic3 = beanScope.get(MyGeneric.class, "genericOther");
+      final MyGeneric<?> generic2 = beanScope.get(MyAutoNameFactory$DI.TYPE_MyGenericString, "genericString");
+      final MyGeneric<?> generic3 = beanScope.get(MyAutoNameFactory$DI.TYPE_MyGenericSome, "genericOther");
       assertThat(genericWild).isNotNull();
       assertThat(generic2).isNotNull();
       assertThat(generic3).isNotNull();
diff --git a/inject-test/src/test/java/org/example/coffee/generic/HazManagerTest.java b/inject-test/src/test/java/org/example/coffee/generic/HazManagerTest.java
index 3b227ee4..b38039ab 100644
--- a/inject-test/src/test/java/org/example/coffee/generic/HazManagerTest.java
+++ b/inject-test/src/test/java/org/example/coffee/generic/HazManagerTest.java
@@ -8,10 +8,10 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.when;
 
-public class HazManagerTest {
+class HazManagerTest {
 
   @Test
-  public void find_when_allWired() {
+  void find_when_allWired() {
     HazManager hazManager = ApplicationScope.get(HazManager.class);
     Haz haz = hazManager.find(42L);
 
@@ -19,7 +19,7 @@ public class HazManagerTest {
   }
 
   @Test
-  public void find_with_mockHaz() {
+  void find_with_mockHaz() {
     try (BeanScope context = BeanScope.builder()
       .forTesting()
       .mock(HazRepo.class)
@@ -33,7 +33,7 @@ public class HazManagerTest {
   }
 
   @Test
-  public void find_with_stubHazUsingMockito() {
+  void find_with_stubHazUsingMockito() {
     try (BeanScope context = BeanScope.builder()
       .forTesting()
       .mock(HazRepo.class, hazRepo -> {
@@ -49,7 +49,7 @@ public class HazManagerTest {
   }
 
   @Test
-  public void withBean_usingGenericType() {
+  void withBean_usingGenericType() {
     TDFoo testDouble = new TDFoo();
 
     try (BeanScope context = BeanScope.builder()
@@ -64,7 +64,7 @@ public class HazManagerTest {
   }
 
   @Test
-  public void find_with_testDouble() {
+  void find_with_testDouble() {
     TDHazRepo testDouble = new TDHazRepo();
 
     try (BeanScope context = BeanScope.builder()
@@ -83,7 +83,6 @@ public class HazManagerTest {
       testDouble.id = 128L;
       haz = hazManager.find(42L);
       assertThat(haz.id).isEqualTo(128L);
-
     }
   }
 
diff --git a/inject-test/src/test/java/org/example/coffee/primary/UseNamedPEmailerTest.java b/inject-test/src/test/java/org/example/coffee/primary/UseNamedPEmailerTest.java
index 76dc2e70..92e90a73 100644
--- a/inject-test/src/test/java/org/example/coffee/primary/UseNamedPEmailerTest.java
+++ b/inject-test/src/test/java/org/example/coffee/primary/UseNamedPEmailerTest.java
@@ -12,6 +12,6 @@ class UseNamedPEmailerTest {
     UseNamedPEmailer bean = ApplicationScope.get(UseNamedPEmailer.class);
     assertThat(bean.emailNamed()).isEqualTo("other");
     assertThat(bean.emailOther()).isEqualTo("other");
-    assertThat(bean.emailOtherPEmailer()).isEqualTo("other");
+    assertThat(bean.emailOtherPEmailer()).isEqualTo("primary");
   }
 }
diff --git a/inject-test/src/test/java/org/example/coffee/provider/BProv.java b/inject-test/src/test/java/org/example/coffee/provider/BProv.java
new file mode 100644
index 00000000..d5785ca0
--- /dev/null
+++ b/inject-test/src/test/java/org/example/coffee/provider/BProv.java
@@ -0,0 +1,14 @@
+package org.example.coffee.provider;
+
+public class BProv<T> {
+
+  final T value;
+
+  public BProv(T value) {
+    this.value = value;
+  }
+
+  public T get() {
+    return value;
+  }
+}
diff --git a/inject-test/src/test/java/org/example/coffee/provider/BProvProvider.java b/inject-test/src/test/java/org/example/coffee/provider/BProvProvider.java
new file mode 100644
index 00000000..d5e0472c
--- /dev/null
+++ b/inject-test/src/test/java/org/example/coffee/provider/BProvProvider.java
@@ -0,0 +1,17 @@
+package org.example.coffee.provider;
+
+import io.avaje.inject.Component;
+import jakarta.inject.Provider;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Component
+public class BProvProvider implements Provider<BProv<String>> {
+
+  AtomicInteger counter = new AtomicInteger();
+
+  @Override
+  public BProv<String> get() {
+    return new BProv<>("Hello BProv" + counter.incrementAndGet());
+  }
+}
diff --git a/inject-test/src/test/java/org/example/coffee/provider/BProvProviderTest.java b/inject-test/src/test/java/org/example/coffee/provider/BProvProviderTest.java
new file mode 100644
index 00000000..3d8185e7
--- /dev/null
+++ b/inject-test/src/test/java/org/example/coffee/provider/BProvProviderTest.java
@@ -0,0 +1,23 @@
+package org.example.coffee.provider;
+
+import io.avaje.inject.BeanScope;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class BProvProviderTest {
+
+  @Test
+  void providerOfGenericType() {
+    try (BeanScope beanScope = BeanScope.builder().build()) {
+
+      BProv<String> bProv = beanScope.get(BProvProvider$DI.TYPE_BProvString, null);
+      assertThat(bProv.get()).isEqualTo("Hello BProv1");
+
+      BProv<String> bProv2 = beanScope.get(BProvProvider$DI.TYPE_BProvString, null);
+      assertThat(bProv).isNotSameAs(bProv2);
+      assertThat(bProv2.get()).isEqualTo("Hello BProv2");
+    }
+  }
+
+}
diff --git a/inject-test/src/test/java/org/example/custom3/ParentScopeTest.java b/inject-test/src/test/java/org/example/custom3/ParentScopeTest.java
index 36db347f..c3862ef7 100644
--- a/inject-test/src/test/java/org/example/custom3/ParentScopeTest.java
+++ b/inject-test/src/test/java/org/example/custom3/ParentScopeTest.java
@@ -4,6 +4,7 @@ import io.avaje.inject.BeanScope;
 import org.example.custom2.*;
 import org.junit.jupiter.api.Test;
 
+import java.lang.reflect.Type;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -39,6 +40,9 @@ class ParentScopeTest {
     final List<OciRock> allRocks = scope.list(OciRock.class);
     assertThat(allRocks).containsOnly(red, ocsOne, ocsTwo);
 
+    final List<OciRock> allRocksAsType = scope.list((Type)OciRock.class);
+    assertThat(allRocksAsType).containsOnly(red, ocsOne, ocsTwo);
+
     // combined from both scopes
     final List<Object> marked = scope.listByAnnotation(OciMarker.class);
     assertThat(marked).containsOnly(red, ocsTwo);
diff --git a/inject-test/src/test/java/org/example/generic/CRUDService.java b/inject-test/src/test/java/org/example/generic/CRUDService.java
new file mode 100644
index 00000000..454ab600
--- /dev/null
+++ b/inject-test/src/test/java/org/example/generic/CRUDService.java
@@ -0,0 +1,6 @@
+package org.example.generic;
+
+public interface CRUDService<T, KeyType> extends ReadService<T, KeyType>, CreateService<T, KeyType> {
+
+  String iamCrud();
+}
diff --git a/inject-test/src/test/java/org/example/generic/CreateService.java b/inject-test/src/test/java/org/example/generic/CreateService.java
new file mode 100644
index 00000000..8d1860c2
--- /dev/null
+++ b/inject-test/src/test/java/org/example/generic/CreateService.java
@@ -0,0 +1,6 @@
+package org.example.generic;
+
+public interface CreateService<T, KeyType> {
+
+  KeyType create(T bean);
+}
diff --git a/inject-test/src/test/java/org/example/generic/GenericTypeTest.java b/inject-test/src/test/java/org/example/generic/GenericTypeTest.java
new file mode 100644
index 00000000..d1471f4b
--- /dev/null
+++ b/inject-test/src/test/java/org/example/generic/GenericTypeTest.java
@@ -0,0 +1,42 @@
+package org.example.generic;
+
+import io.avaje.inject.spi.GenericType;
+import org.example.iface.IfaceParam;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Type;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class GenericTypeTest {
+
+  static final Type TYPE_IfaceParamClass = IfaceParam.class;
+  static final Type TYPE_IfaceParamNone = new GenericType<IfaceParam>(){};
+  static final Type TYPE_IfaceParamWild = new GenericType<IfaceParam<?>>(){};
+  static final Type TYPE_IfaceParamGeneric = new GenericType<IfaceParam<Integer>>(){};
+
+  @Test
+  void getTypeName() {
+    assertThat(TYPE_IfaceParamClass.getTypeName()).isEqualTo("org.example.iface.IfaceParam");
+    assertThat(TYPE_IfaceParamNone.getTypeName()).isEqualTo("org.example.iface.IfaceParam");
+    assertThat(TYPE_IfaceParamWild.getTypeName()).isEqualTo("org.example.iface.IfaceParam<?>");
+    assertThat(TYPE_IfaceParamGeneric.getTypeName()).isEqualTo("org.example.iface.IfaceParam<java.lang.Integer>");
+  }
+
+  @SuppressWarnings("rawtypes")
+  @Test
+  void rawType_equals() {
+    Type typeClass = IfaceParam.class;
+    Type typeGenericRaw = new GenericType<IfaceParam>(){}.type();
+    assertThat(typeClass).isEqualTo(typeGenericRaw);
+  }
+
+  @SuppressWarnings("rawtypes")
+  @Test
+  void rawType_notEqualTo_wildType() {
+    Type typeGenericRaw = new GenericType<IfaceParam>(){}.type();
+    Type typeGenericWild = new GenericType<IfaceParam<?>>(){}.type();
+    assertThat(typeGenericRaw).isNotEqualTo(typeGenericWild);
+  }
+
+}
diff --git a/inject-test/src/test/java/org/example/generic/MyCreateService.java b/inject-test/src/test/java/org/example/generic/MyCreateService.java
new file mode 100644
index 00000000..569d71d6
--- /dev/null
+++ b/inject-test/src/test/java/org/example/generic/MyCreateService.java
@@ -0,0 +1,12 @@
+package org.example.generic;
+
+import io.avaje.inject.Component;
+
+@Component
+class MyCreateService implements CreateService<MyObj, Integer> {
+
+  @Override
+  public Integer create(MyObj bean) {
+    return 42;
+  }
+}
diff --git a/inject-test/src/test/java/org/example/generic/MyCrudService.java b/inject-test/src/test/java/org/example/generic/MyCrudService.java
new file mode 100644
index 00000000..e3d4e20b
--- /dev/null
+++ b/inject-test/src/test/java/org/example/generic/MyCrudService.java
@@ -0,0 +1,32 @@
+package org.example.generic;
+
+import io.avaje.inject.Component;
+
+import java.util.Optional;
+
+@Component
+public class MyCrudService implements CRUDService<MyObj, Integer> {
+
+  final ReadService<MyObj, Integer> read;
+  final CreateService<MyObj, Integer> create;
+
+  public MyCrudService(ReadService<MyObj, Integer> read, CreateService<MyObj, Integer> create) {
+    this.read = read;
+    this.create = create;
+  }
+
+  @Override
+  public String iamCrud() {
+    return "MyCrud";
+  }
+
+  @Override
+  public Integer create(MyObj bean) {
+    return create.create(bean);
+  }
+
+  @Override
+  public Optional<MyObj> get(Integer id) {
+    return read.get(id);
+  }
+}
diff --git a/inject-test/src/test/java/org/example/generic/MyCrudServiceTest.java b/inject-test/src/test/java/org/example/generic/MyCrudServiceTest.java
new file mode 100644
index 00000000..801b5391
--- /dev/null
+++ b/inject-test/src/test/java/org/example/generic/MyCrudServiceTest.java
@@ -0,0 +1,26 @@
+package org.example.generic;
+
+import io.avaje.inject.BeanScope;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class MyCrudServiceTest {
+
+  @Test
+  void test_genericTypesInjectedAsExpected() {
+    try (BeanScope beanScope = BeanScope.builder().build()) {
+      MyReadService readService = beanScope.get(MyReadService.class);
+      MyCreateService createService = beanScope.get(MyCreateService.class);
+      MyCrudService crudService = beanScope.get(MyCrudService.class);
+
+      assertNotNull(readService);
+      assertNotNull(createService);
+      assertNotNull(crudService);
+
+      assertThat(crudService.create).isSameAs(createService);
+      assertThat(crudService.read).isSameAs(readService);
+    }
+  }
+}
diff --git a/inject-test/src/test/java/org/example/generic/MyObj.java b/inject-test/src/test/java/org/example/generic/MyObj.java
new file mode 100644
index 00000000..a321ebeb
--- /dev/null
+++ b/inject-test/src/test/java/org/example/generic/MyObj.java
@@ -0,0 +1,4 @@
+package org.example.generic;
+
+public class MyObj {
+}
diff --git a/inject-test/src/test/java/org/example/generic/MyReadService.java b/inject-test/src/test/java/org/example/generic/MyReadService.java
new file mode 100644
index 00000000..adfd9be9
--- /dev/null
+++ b/inject-test/src/test/java/org/example/generic/MyReadService.java
@@ -0,0 +1,14 @@
+package org.example.generic;
+
+import io.avaje.inject.Component;
+
+import java.util.Optional;
+
+@Component
+public class MyReadService implements ReadService<MyObj, Integer> {
+
+  @Override
+  public Optional<MyObj> get(Integer id) {
+    return Optional.empty();
+  }
+}
diff --git a/inject-test/src/test/java/org/example/generic/ReadService.java b/inject-test/src/test/java/org/example/generic/ReadService.java
new file mode 100644
index 00000000..882e32dd
--- /dev/null
+++ b/inject-test/src/test/java/org/example/generic/ReadService.java
@@ -0,0 +1,7 @@
+package org.example.generic;
+
+import java.util.Optional;
+
+public interface ReadService<T, KeyType> {
+    Optional<T> get(KeyType id);
+}
diff --git a/inject-test/src/test/java/org/example/request/CController.java b/inject-test/src/test/java/org/example/request/CController.java
new file mode 100644
index 00000000..49c5d064
--- /dev/null
+++ b/inject-test/src/test/java/org/example/request/CController.java
@@ -0,0 +1,28 @@
+package org.example.request;
+
+import io.avaje.http.api.Controller;
+import io.helidon.webserver.ServerRequest;
+import io.helidon.webserver.ServerResponse;
+import org.example.generic.MyObj;
+import org.example.generic.ReadService;
+
+/**
+ * Controller with request scoped dependencies and generic parameter.
+ */
+@Controller
+public class CController {
+
+  final ReadService<MyObj, Integer> readService;
+  final ServerRequest request;
+  final ServerResponse response;
+
+  CController(ReadService<MyObj, Integer> readService, ServerRequest request, ServerResponse response) {
+    this.readService = readService;
+    this.request = request;
+    this.response = response;
+  }
+
+  public String get() {
+    return "hi " + request.toString() + response.toString() + readService.toString();
+  }
+}
diff --git a/inject/src/test/java/io/avaje/inject/BeanScopeBuilderTest.java b/inject/src/test/java/io/avaje/inject/BeanScopeBuilderTest.java
index 42800045..4db07933 100644
--- a/inject/src/test/java/io/avaje/inject/BeanScopeBuilderTest.java
+++ b/inject/src/test/java/io/avaje/inject/BeanScopeBuilderTest.java
@@ -272,6 +272,11 @@ class BeanScopeBuilderTest {
       return null;
     }
 
+    @Override
+    public <T> List<T> list(Type interfaceType) {
+      return null;
+    }
+
     @Override
     public <T> List<T> listByPriority(Class<T> interfaceType) {
       return null;