DI improvements (#1717)

* Code cleanup
* Provide build path with causes when an exception occur
* Fix toString to display meaningful info
* Javadoc
* Add @NonNull and @Overrides annotations
* Support for @Nullable on fields and parameters
diff --git a/api/maven-api-di/src/main/java/org/apache/maven/api/di/MojoExecutionScoped.java b/api/maven-api-di/src/main/java/org/apache/maven/api/di/MojoExecutionScoped.java
index a3fec5c..729ec5e 100644
--- a/api/maven-api-di/src/main/java/org/apache/maven/api/di/MojoExecutionScoped.java
+++ b/api/maven-api-di/src/main/java/org/apache/maven/api/di/MojoExecutionScoped.java
@@ -29,6 +29,13 @@
 /**
  * Indicates that the annotated bean has a lifespan limited to a given mojo execution,
  * which means each mojo execution will result in a different instance being injected.
+ * <p>
+ * The following objects will be bound to the mojo execution scope:
+ *     <ul>
+ *         <li>{@code org.apache.maven.api.MojoExecution}</li>
+ *         <li>{@code org.apache.maven.api.Project}</li>
+ *         <li>{@code org.apache.maven.api.plugin.Log}</li>
+ *     </ul>
  *
  * @since 4.0.0
  */
diff --git a/api/maven-api-di/src/main/java/org/apache/maven/api/di/SessionScoped.java b/api/maven-api-di/src/main/java/org/apache/maven/api/di/SessionScoped.java
index eae72f8..06c1049 100644
--- a/api/maven-api-di/src/main/java/org/apache/maven/api/di/SessionScoped.java
+++ b/api/maven-api-di/src/main/java/org/apache/maven/api/di/SessionScoped.java
@@ -29,6 +29,8 @@
 /**
  * Indicates that annotated component should be instantiated before session execution starts
  * and discarded after session execution completes.
+ * <p>
+ * A {@code org.apache.maven.api.Session} object is available in the scope of this annotation.
  *
  * @since 4.0.0
  */
diff --git a/api/maven-api-meta/src/main/java/org/apache/maven/api/annotations/Nullable.java b/api/maven-api-meta/src/main/java/org/apache/maven/api/annotations/Nullable.java
index 32e4bd7..df2517c 100644
--- a/api/maven-api-meta/src/main/java/org/apache/maven/api/annotations/Nullable.java
+++ b/api/maven-api-meta/src/main/java/org/apache/maven/api/annotations/Nullable.java
@@ -30,5 +30,5 @@
  */
 @Experimental
 @Documented
-@Retention(RetentionPolicy.CLASS)
+@Retention(RetentionPolicy.RUNTIME)
 public @interface Nullable {}
diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java b/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java
index ab12ee5..eb6789c 100644
--- a/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java
+++ b/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java
@@ -48,6 +48,7 @@
 import org.apache.maven.di.Key;
 import org.apache.maven.di.impl.Binding;
 import org.apache.maven.di.impl.DIException;
+import org.apache.maven.di.impl.Dependency;
 import org.apache.maven.di.impl.InjectorImpl;
 import org.apache.maven.execution.scope.internal.MojoExecutionScope;
 import org.apache.maven.session.scope.internal.SessionScope;
@@ -66,7 +67,8 @@ protected void configure() {
 
         injector = new InjectorImpl() {
             @Override
-            public <Q> Supplier<Q> getCompiledBinding(Key<Q> key) {
+            public <Q> Supplier<Q> getCompiledBinding(Dependency<Q> dep) {
+                Key<Q> key = dep.key();
                 Set<Binding<Q>> res = getBindings(key);
                 if (res != null && !res.isEmpty()) {
                     List<Binding<Q>> bindingList = new ArrayList<>(res);
@@ -119,6 +121,9 @@ public <Q> Supplier<Q> getCompiledBinding(Key<Q> key) {
                     // ignore
                     e.printStackTrace();
                 }
+                if (dep.optional()) {
+                    return () -> null;
+                }
                 throw new DIException("No binding to construct an instance for key "
                         + key.getDisplayString() + ".  Existing bindings:\n"
                         + getBoundKeys().stream()
diff --git a/maven-di/src/main/java/org/apache/maven/di/Injector.java b/maven-di/src/main/java/org/apache/maven/di/Injector.java
index 9ccc1b6..d4eea98 100644
--- a/maven-di/src/main/java/org/apache/maven/di/Injector.java
+++ b/maven-di/src/main/java/org/apache/maven/di/Injector.java
@@ -21,6 +21,7 @@
 import java.lang.annotation.Annotation;
 import java.util.function.Supplier;
 
+import org.apache.maven.api.annotations.Nonnull;
 import org.apache.maven.di.impl.InjectorImpl;
 
 public interface Injector {
@@ -29,18 +30,24 @@ public interface Injector {
     // Builder API
     //
 
+    @Nonnull
     static Injector create() {
         return new InjectorImpl();
     }
 
+    @Nonnull
     Injector discover(ClassLoader classLoader);
 
+    @Nonnull
     Injector bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope);
 
+    @Nonnull
     Injector bindScope(Class<? extends Annotation> scopeAnnotation, Supplier<Scope> scope);
 
+    @Nonnull
     Injector bindImplicit(Class<?> cls);
 
+    @Nonnull
     <T> Injector bindInstance(Class<T> cls, T instance);
 
     //
diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java b/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java
index 97e650a..204c07a 100644
--- a/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java
+++ b/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java
@@ -34,16 +34,16 @@
 import static java.util.stream.Collectors.joining;
 
 public abstract class Binding<T> {
-    private final Set<Key<?>> dependencies;
+    private final Set<Dependency<?>> dependencies;
     private Annotation scope;
     private int priority;
     private Key<?> originalKey;
 
-    protected Binding(Key<? extends T> originalKey, Set<Key<?>> dependencies) {
+    protected Binding(Key<? extends T> originalKey, Set<Dependency<?>> dependencies) {
         this(originalKey, dependencies, null, 0);
     }
 
-    protected Binding(Key<?> originalKey, Set<Key<?>> dependencies, Annotation scope, int priority) {
+    protected Binding(Key<?> originalKey, Set<Dependency<?>> dependencies, Annotation scope, int priority) {
         this.originalKey = originalKey;
         this.dependencies = dependencies;
         this.scope = scope;
@@ -56,15 +56,18 @@ public static <T> Binding<T> toInstance(T instance) {
 
     public static <R> Binding<R> to(Key<R> originalKey, TupleConstructorN<R> constructor, Class<?>[] types) {
         return Binding.to(
-                originalKey, constructor, Stream.of(types).map(Key::of).toArray(Key<?>[]::new));
+                originalKey,
+                constructor,
+                Stream.of(types).map(c -> new Dependency<>(Key.of(c), false)).toArray(Dependency<?>[]::new));
     }
 
-    public static <R> Binding<R> to(Key<R> originalKey, TupleConstructorN<R> constructor, Key<?>[] dependencies) {
+    public static <R> Binding<R> to(
+            Key<R> originalKey, TupleConstructorN<R> constructor, Dependency<?>[] dependencies) {
         return to(originalKey, constructor, dependencies, 0);
     }
 
     public static <R> Binding<R> to(
-            Key<R> originalKey, TupleConstructorN<R> constructor, Key<?>[] dependencies, int priority) {
+            Key<R> originalKey, TupleConstructorN<R> constructor, Dependency<?>[] dependencies, int priority) {
         return new BindingToConstructor<>(originalKey, constructor, dependencies, priority);
     }
 
@@ -94,13 +97,17 @@ public Binding<T> initializeWith(BindingInitializer<T> bindingInitializer) {
                 this.scope,
                 this.priority) {
             @Override
-            public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
+            public Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
                 final Supplier<T> compiledBinding = Binding.this.compile(compiler);
                 final Consumer<T> consumer = bindingInitializer.compile(compiler);
                 return () -> {
-                    T instance = compiledBinding.get();
-                    consumer.accept(instance);
-                    return instance;
+                    try {
+                        T instance = compiledBinding.get();
+                        consumer.accept(instance);
+                        return instance;
+                    } catch (DIException e) {
+                        throw new DIException("Error while initializing binding " + Binding.this, e);
+                    }
                 };
             }
 
@@ -111,9 +118,9 @@ public String toString() {
         };
     }
 
-    public abstract Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler);
+    public abstract Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler);
 
-    public Set<Key<?>> getDependencies() {
+    public Set<Dependency<?>> getDependencies() {
         return dependencies;
     }
 
@@ -122,7 +129,7 @@ public Annotation getScope() {
     }
 
     public String getDisplayString() {
-        return dependencies.stream().map(Key::getDisplayString).collect(joining(", ", "[", "]"));
+        return dependencies.stream().map(Dependency::getDisplayString).collect(joining(", ", "[", "]"));
     }
 
     public Key<?> getOriginalKey() {
@@ -152,7 +159,7 @@ public BindingToInstance(T instance) {
         }
 
         @Override
-        public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
+        public Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
             return () -> instance;
         }
 
@@ -164,17 +171,17 @@ public String toString() {
 
     public static class BindingToConstructor<T> extends Binding<T> {
         final TupleConstructorN<T> constructor;
-        final Key<?>[] args;
+        final Dependency<?>[] args;
 
         BindingToConstructor(
-                Key<? extends T> key, TupleConstructorN<T> constructor, Key<?>[] dependencies, int priority) {
+                Key<? extends T> key, TupleConstructorN<T> constructor, Dependency<?>[] dependencies, int priority) {
             super(key, new HashSet<>(Arrays.asList(dependencies)), null, priority);
             this.constructor = constructor;
             this.args = dependencies;
         }
 
         @Override
-        public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
+        public Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
             return () -> {
                 Object[] args =
                         Stream.of(this.args).map(compiler).map(Supplier::get).toArray();
@@ -184,7 +191,7 @@ public Supplier<T> compile(Function<Key<?>, Supplier<?>> compiler) {
 
         @Override
         public String toString() {
-            return "BindingToConstructor[" + constructor + "]" + getDependencies();
+            return "BindingToConstructor[" + getOriginalKey() + "]" + getDependencies();
         }
     }
 }
diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java b/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java
index a123f30..ccf5530 100644
--- a/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java
+++ b/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java
@@ -25,32 +25,30 @@
 import java.util.function.Function;
 import java.util.function.Supplier;
 
-import org.apache.maven.di.Key;
-
 import static java.util.stream.Collectors.toSet;
 
 public abstract class BindingInitializer<T> {
 
-    private final Set<Key<?>> dependencies;
+    private final Set<Dependency<?>> dependencies;
 
-    protected BindingInitializer(Set<Key<?>> dependencies) {
+    protected BindingInitializer(Set<Dependency<?>> dependencies) {
         this.dependencies = dependencies;
     }
 
-    public Set<Key<?>> getDependencies() {
+    public Set<Dependency<?>> getDependencies() {
         return dependencies;
     }
 
-    public abstract Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler);
+    public abstract Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler);
 
     public static <T> BindingInitializer<T> combine(List<BindingInitializer<T>> bindingInitializers) {
-        Set<Key<?>> deps = bindingInitializers.stream()
+        Set<Dependency<?>> deps = bindingInitializers.stream()
                 .map(BindingInitializer::getDependencies)
                 .flatMap(Collection::stream)
                 .collect(toSet());
-        return new BindingInitializer<T>(deps) {
+        return new BindingInitializer<>(deps) {
             @Override
-            public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
+            public Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
                 return instance -> bindingInitializers.stream()
                         .map(bindingInitializer -> bindingInitializer.compile(compiler))
                         .forEach(i -> i.accept(instance));
diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/Dependency.java b/maven-di/src/main/java/org/apache/maven/di/impl/Dependency.java
new file mode 100644
index 0000000..4dc9052
--- /dev/null
+++ b/maven-di/src/main/java/org/apache/maven/di/impl/Dependency.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.di.impl;
+
+import org.apache.maven.di.Key;
+
+public record Dependency<T>(Key<T> key, boolean optional) {
+    public String getDisplayString() {
+        String s = key.getDisplayString();
+        if (optional) {
+            s = "?" + s;
+        }
+        return s;
+    }
+}
diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java b/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java
index 143f521..1e047f5 100644
--- a/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java
+++ b/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java
@@ -64,12 +64,14 @@ public InjectorImpl() {
         bindScope(Singleton.class, new SingletonScope());
     }
 
+    @Override
     public <T> T getInstance(Class<T> key) {
         return getInstance(Key.of(key));
     }
 
+    @Override
     public <T> T getInstance(Key<T> key) {
-        return getCompiledBinding(key).get();
+        return getCompiledBinding(new Dependency<>(key, false)).get();
     }
 
     @SuppressWarnings("unchecked")
@@ -88,7 +90,7 @@ public Injector discover(ClassLoader classLoader) {
                 try (InputStream is = enumeration.nextElement().openStream();
                         BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)))) {
                     for (String line :
-                            reader.lines().filter(l -> !l.startsWith("#")).collect(Collectors.toList())) {
+                            reader.lines().filter(l -> !l.startsWith("#")).toList()) {
                         Class<?> clazz = classLoader.loadClass(line);
                         bindImplicit(clazz);
                     }
@@ -100,10 +102,12 @@ public Injector discover(ClassLoader classLoader) {
         return this;
     }
 
+    @Override
     public Injector bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope) {
         return bindScope(scopeAnnotation, () -> scope);
     }
 
+    @Override
     public Injector bindScope(Class<? extends Annotation> scopeAnnotation, Supplier<Scope> scope) {
         if (scopes.put(scopeAnnotation, scope) != null) {
             throw new DIException(
@@ -112,6 +116,7 @@ public Injector bindScope(Class<? extends Annotation> scopeAnnotation, Supplier<
         return this;
     }
 
+    @Override
     public <U> Injector bindInstance(Class<U> clazz, U instance) {
         Key<?> key = Key.of(clazz, ReflectionUtils.qualifierOf(clazz));
         Binding<U> binding = Binding.toInstance(instance);
@@ -133,7 +138,7 @@ public Injector bindImplicit(Class<?> clazz) {
         return this;
     }
 
-    private LinkedHashSet<Key<?>> current = new LinkedHashSet<>();
+    private final LinkedHashSet<Key<?>> current = new LinkedHashSet<>();
 
     private Injector doBind(Key<?> key, Binding<?> binding) {
         if (!current.add(key)) {
@@ -175,7 +180,8 @@ public Map<Key<?>, Set<Binding<?>>> getBindings() {
         return bindings;
     }
 
-    public <Q> Supplier<Q> getCompiledBinding(Key<Q> key) {
+    public <Q> Supplier<Q> getCompiledBinding(Dependency<Q> dep) {
+        Key<Q> key = dep.key();
         Set<Binding<Q>> res = getBindings(key);
         if (res != null && !res.isEmpty()) {
             List<Binding<Q>> bindingList = new ArrayList<>(res);
@@ -211,6 +217,9 @@ public <Q> Supplier<Q> getCompiledBinding(Key<Q> key) {
                 return () -> (Q) map(map);
             }
         }
+        if (dep.optional()) {
+            return () -> null;
+        }
         throw new DIException("No binding to construct an instance for key "
                 + key.getDisplayString() + ".  Existing bindings:\n"
                 + getBoundKeys().stream()
@@ -312,14 +321,13 @@ private static class WrappingMap<K, V, T> extends AbstractMap<K, V> {
             this.mapper = mapper;
         }
 
-        @SuppressWarnings("NullableProblems")
         @Override
         public Set<Entry<K, V>> entrySet() {
-            return new AbstractSet<Entry<K, V>>() {
+            return new AbstractSet<>() {
                 @Override
                 public Iterator<Entry<K, V>> iterator() {
                     Iterator<Entry<K, T>> it = delegate.entrySet().iterator();
-                    return new Iterator<Entry<K, V>>() {
+                    return new Iterator<>() {
                         @Override
                         public boolean hasNext() {
                             return it.hasNext();
diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java b/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java
index 4389138..3d49a28 100644
--- a/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java
+++ b/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java
@@ -171,14 +171,14 @@ public static <T extends AnnotatedElement & Member> List<T> getAnnotatedElements
         List<Constructor<?>> constructors = Arrays.asList(cls.getDeclaredConstructors());
         List<Constructor<?>> injectConstructors = constructors.stream()
                 .filter(c -> c.isAnnotationPresent(Inject.class))
-                .collect(toList());
+                .toList();
 
         List<Method> factoryMethods = Arrays.stream(cls.getDeclaredMethods())
                 .filter(method -> method.getReturnType() == cls && Modifier.isStatic(method.getModifiers()))
-                .collect(toList());
+                .toList();
         List<Method> injectFactoryMethods = factoryMethods.stream()
                 .filter(method -> method.isAnnotationPresent(Inject.class))
-                .collect(toList());
+                .toList();
 
         if (!injectConstructors.isEmpty()) {
             if (injectConstructors.size() > 1) {
@@ -240,10 +240,12 @@ public static <T> BindingInitializer<T> generateInjectingInitializer(Key<T> cont
     public static <T> BindingInitializer<T> fieldInjector(Key<T> container, Field field) {
         field.setAccessible(true);
         Key<Object> key = keyOf(container.getType(), field.getGenericType(), field);
-        return new BindingInitializer<T>(Collections.singleton(key)) {
+        boolean optional = field.isAnnotationPresent(Nullable.class);
+        Dependency<Object> dep = new Dependency<>(key, optional);
+        return new BindingInitializer<T>(Collections.singleton(dep)) {
             @Override
-            public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
-                Supplier<?> binding = compiler.apply(key);
+            public Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
+                Supplier<?> binding = compiler.apply(dep);
                 return (T instance) -> {
                     Object arg = binding.get();
                     try {
@@ -258,10 +260,10 @@ public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
 
     public static <T> BindingInitializer<T> methodInjector(Key<T> container, Method method) {
         method.setAccessible(true);
-        Key<?>[] dependencies = toDependencies(container.getType(), method);
+        Dependency<?>[] dependencies = toDependencies(container.getType(), method);
         return new BindingInitializer<T>(new HashSet<>(Arrays.asList(dependencies))) {
             @Override
-            public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
+            public Consumer<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
                 return instance -> {
                     Object[] args = getDependencies().stream()
                             .map(compiler)
@@ -279,35 +281,31 @@ public Consumer<T> compile(Function<Key<?>, Supplier<?>> compiler) {
         };
     }
 
-    public static Key<?>[] toDependencies(@Nullable Type container, Executable executable) {
-        Key<?>[] keys = toArgDependencies(container, executable);
+    public static Dependency<?>[] toDependencies(@Nullable Type container, Executable executable) {
+        Dependency<?>[] keys = toArgDependencies(container, executable);
         if (executable instanceof Constructor || Modifier.isStatic(executable.getModifiers())) {
             return keys;
         } else {
-            Key<?>[] nkeys = new Key[keys.length + 1];
-            nkeys[0] = Key.ofType(container);
+            Dependency<?>[] nkeys = new Dependency[keys.length + 1];
+            nkeys[0] = new Dependency<>(Key.ofType(container), false);
             System.arraycopy(keys, 0, nkeys, 1, keys.length);
             return nkeys;
         }
     }
 
-    private static Key<?>[] toArgDependencies(@Nullable Type container, Executable executable) {
+    private static Dependency<?>[] toArgDependencies(@Nullable Type container, Executable executable) {
         Parameter[] parameters = executable.getParameters();
-        Key<?>[] dependencies = new Key<?>[parameters.length];
+        Dependency<?>[] dependencies = new Dependency<?>[parameters.length];
         if (parameters.length == 0) {
             return dependencies;
         }
 
-        Type type = parameters[0].getParameterizedType();
-        Parameter parameter = parameters[0];
-        dependencies[0] = keyOf(container, type, parameter);
-
         Type[] genericParameterTypes = executable.getGenericParameterTypes();
-        boolean hasImplicitDependency = genericParameterTypes.length != parameters.length;
-        for (int i = 1; i < dependencies.length; i++) {
-            type = genericParameterTypes[hasImplicitDependency ? i - 1 : i];
-            parameter = parameters[i];
-            dependencies[i] = keyOf(container, type, parameter);
+        for (int i = 0; i < dependencies.length; i++) {
+            Type type = genericParameterTypes[i];
+            Parameter parameter = parameters[i];
+            boolean optional = parameter.isAnnotationPresent(Nullable.class);
+            dependencies[i] = new Dependency<>(keyOf(container, type, parameter), optional);
         }
         return dependencies;
     }
@@ -353,7 +351,7 @@ public static <T> Binding<T> bindingFromMethod(Method method) {
     public static <T> Binding<T> bindingFromConstructor(Key<T> key, Constructor<T> constructor) {
         constructor.setAccessible(true);
 
-        Key<?>[] dependencies = toDependencies(key.getType(), constructor);
+        Dependency<?>[] dependencies = toDependencies(key.getType(), constructor);
 
         Binding<T> binding = Binding.to(
                 key,
diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/Types.java b/maven-di/src/main/java/org/apache/maven/di/impl/Types.java
index 735057b..b4830bd 100644
--- a/maven-di/src/main/java/org/apache/maven/di/impl/Types.java
+++ b/maven-di/src/main/java/org/apache/maven/di/impl/Types.java
@@ -186,8 +186,7 @@ public static Type bind(Type type, Function<TypeVariable<?>, Type> bindings) {
         if (type instanceof Class) {
             return type;
         }
-        if (type instanceof TypeVariable<?>) {
-            TypeVariable<?> typeVariable = (TypeVariable<?>) type;
+        if (type instanceof TypeVariable<?> typeVariable) {
             Type actualType = bindings.apply(typeVariable);
             if (actualType == null) {
                 throw new TypeNotBoundException("Type variable not found: " + typeVariable + " ( "
@@ -195,8 +194,7 @@ public static Type bind(Type type, Function<TypeVariable<?>, Type> bindings) {
             }
             return actualType;
         }
-        if (type instanceof ParameterizedType) {
-            ParameterizedType parameterizedType = (ParameterizedType) type;
+        if (type instanceof ParameterizedType parameterizedType) {
             Type[] typeArguments = parameterizedType.getActualTypeArguments();
             Type[] typeArguments2 = new Type[typeArguments.length];
             for (int i = 0; i < typeArguments.length; i++) {
@@ -209,8 +207,7 @@ public static Type bind(Type type, Function<TypeVariable<?>, Type> bindings) {
             Type componentType = ((GenericArrayType) type).getGenericComponentType();
             return new GenericArrayTypeImpl(bind(componentType, bindings));
         }
-        if (type instanceof WildcardType) {
-            WildcardType wildcardType = (WildcardType) type;
+        if (type instanceof WildcardType wildcardType) {
             Type[] upperBounds = wildcardType.getUpperBounds();
             Type[] upperBounds2 = new Type[upperBounds.length];
             for (int i = 0; i < upperBounds.length; i++) {
@@ -309,8 +306,7 @@ public static Type simplifyType(Type original) {
             return original;
         }
 
-        if (original instanceof ParameterizedType) {
-            ParameterizedType parameterizedType = (ParameterizedType) original;
+        if (original instanceof ParameterizedType parameterizedType) {
             Type[] typeArguments = parameterizedType.getActualTypeArguments();
             Type[] repackedTypeArguments = simplifyTypes(typeArguments);
 
@@ -329,8 +325,7 @@ public static Type simplifyType(Type original) {
             throw new IllegalArgumentException("Key should not contain a type variable: " + original);
         }
 
-        if (original instanceof WildcardType) {
-            WildcardType wildcardType = (WildcardType) original;
+        if (original instanceof WildcardType wildcardType) {
             Type[] upperBounds = wildcardType.getUpperBounds();
             if (upperBounds.length == 1) {
                 Type upperBound = upperBounds[0];
@@ -398,8 +393,7 @@ public static boolean isAssignable(Type to, Type from) {
     private static boolean isAssignable(Type to, Type from, boolean strict) {
         if (to instanceof WildcardType || from instanceof WildcardType) {
             Type[] toUppers, toLowers;
-            if (to instanceof WildcardType) {
-                WildcardType wildcardTo = (WildcardType) to;
+            if (to instanceof WildcardType wildcardTo) {
                 toUppers = wildcardTo.getUpperBounds();
                 toLowers = wildcardTo.getLowerBounds();
             } else {
@@ -408,10 +402,9 @@ private static boolean isAssignable(Type to, Type from, boolean strict) {
             }
 
             Type[] fromUppers, fromLowers;
-            if (from instanceof WildcardType) {
-                WildcardType wildcardTo = (WildcardType) to;
-                fromUppers = wildcardTo.getUpperBounds();
-                fromLowers = wildcardTo.getLowerBounds();
+            if (from instanceof WildcardType wildcardFrom) {
+                fromUppers = wildcardFrom.getUpperBounds();
+                fromLowers = wildcardFrom.getLowerBounds();
             } else {
                 fromUppers = new Type[] {from};
                 fromLowers = strict ? fromUppers : NO_TYPES;
@@ -523,10 +516,9 @@ public int hashCode() {
 
         @Override
         public boolean equals(Object other) {
-            if (!(other instanceof ParameterizedType)) {
+            if (!(other instanceof ParameterizedType that)) {
                 return false;
             }
-            ParameterizedType that = (ParameterizedType) other;
             return this.getRawType().equals(that.getRawType())
                     && Objects.equals(this.getOwnerType(), that.getOwnerType())
                     && Arrays.equals(this.getActualTypeArguments(), that.getActualTypeArguments());
@@ -613,10 +605,9 @@ public int hashCode() {
 
         @Override
         public boolean equals(Object other) {
-            if (!(other instanceof WildcardType)) {
+            if (!(other instanceof WildcardType that)) {
                 return false;
             }
-            WildcardType that = (WildcardType) other;
             return Arrays.equals(this.getUpperBounds(), that.getUpperBounds())
                     && Arrays.equals(this.getLowerBounds(), that.getLowerBounds());
         }
@@ -671,10 +662,9 @@ public int hashCode() {
 
         @Override
         public boolean equals(Object other) {
-            if (!(other instanceof GenericArrayType)) {
+            if (!(other instanceof GenericArrayType that)) {
                 return false;
             }
-            GenericArrayType that = (GenericArrayType) other;
             return this.getGenericComponentType().equals(that.getGenericComponentType());
         }
 
@@ -700,8 +690,7 @@ public static String getSimpleName(Type type) {
             return Arrays.stream(((ParameterizedType) type).getActualTypeArguments())
                     .map(Types::getSimpleName)
                     .collect(joining(",", "<", ">"));
-        } else if (type instanceof WildcardType) {
-            WildcardType wildcardType = (WildcardType) type;
+        } else if (type instanceof WildcardType wildcardType) {
             Type[] upperBounds = wildcardType.getUpperBounds();
             Type[] lowerBounds = wildcardType.getLowerBounds();
             return "?"
diff --git a/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java b/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java
index b732fe8..0d22acf 100644
--- a/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java
+++ b/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java
@@ -25,6 +25,7 @@
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.maven.api.annotations.Nullable;
 import org.apache.maven.api.di.Inject;
 import org.apache.maven.api.di.Named;
 import org.apache.maven.api.di.Priority;
@@ -42,6 +43,7 @@
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 @SuppressWarnings("unused")
 public class InjectorImplTest {
@@ -328,4 +330,49 @@ static class Another {}
         @Named
         static class Third {}
     }
+
+    @Test
+    void testNullableOnField() {
+        Injector injector = Injector.create().bindImplicit(NullableOnField.class);
+        NullableOnField.MyMojo mojo = injector.getInstance(NullableOnField.MyMojo.class);
+        assertNotNull(mojo);
+        assertNull(mojo.service);
+    }
+
+    static class NullableOnField {
+
+        @Named
+        interface MyService {}
+
+        @Named
+        static class MyMojo {
+            @Inject
+            @Nullable
+            MyService service;
+        }
+    }
+
+    @Test
+    void testNullableOnConstructor() {
+        Injector injector = Injector.create().bindImplicit(NullableOnConstructor.class);
+        NullableOnConstructor.MyMojo mojo = injector.getInstance(NullableOnConstructor.MyMojo.class);
+        assertNotNull(mojo);
+        assertNull(mojo.service);
+    }
+
+    static class NullableOnConstructor {
+
+        @Named
+        interface MyService {}
+
+        @Named
+        static class MyMojo {
+            private final MyService service;
+
+            @Inject
+            public MyMojo(@Nullable MyService service) {
+                this.service = service;
+            }
+        }
+    }
 }