Comparing the Dependency Injection mechanisms of Spring and Jakarta EE (part two).

Photo by Markus Spiske, https://unsplash.com/photos/ZGCnQJRxh4w

The article “Spring vs. Jakarta EE: Defining Injectable Beans” addressed the “first half” of Dependency Injection in Spring in Jakarta EE: the definition of beans that shall be injected. The current article discusses the details of how these beans can be injected. This forms the second part of the dependency injection process, which can be further broken down into the following aspects:

  • Annotations: Which annotations can be used for injection?
  • Disambiguation: How to perform disambiguation if there are multiple candidates for injection?
  • Optional and Multiple Injection: How to gracefully deal with the absence of injectable beans, and how to inject several beans of the same type?
  • Programmatic Injection: How to inject dependencies without annotations?

For the impatient, here is a summary table for these topics, with more details following below:

Topic Spring Jakarta EE
annotations @Autowired, @Inject, @Resource, @Value, @Bean @Inject, @Resource, @Produces, others (JPA/EJB)
disambiguation @Primary, @Qualifier, bean name, @Named, generics @Vetoed, @Alternative, @Qualifier, @Named
optional / multiple @Autowired.required, Optional, @Nullable, Collection / Map / array, ObjectFactory & ObjectProvider Provider & Instance
programmatic BeanFactory CDI

Blog content:

Spring: Annotations

@Autowired

The most important annotation for injecting dependencies in Spring is @Autowired, which can be applied to the following elements:

// field injection
@Autowired
MyBean myBean;
// constructor injection
@Autowired
SomeClass(MyBean myBean) { ... }
// method injection
@Autowired
void someMethod(MyBean myBean) { ... }

Injection can be performed regardless of the access modifier (i.e., you don’t have to use “public” for injection to work).

Constructors and methods can declare multiple parameters to be provided by injection.

For classes that have only one constructor, the @Autowired annotation can be omitted. For classes that have multiple constructors, Spring chooses the right one for injection by following these rules:

  • If only one of the constructors is annotated with @Autowired, choose this one.
  • If several constructors are annotated with @Autowired, choose the one with the largest number of parameters. Note that
    • the @Autowired annotation of all constructors must have the attribute required = false (more on this below)
    • constructors with one or more unsatisfiable parameters are not considered

The Spring team “generally advocates constructor injection” and recommends that “setter injection should primarily only be used for optional dependencies”. If the use of Lombok is allowed in your project, this approach can be further streamlined as described by Victor Rentea in his blog post “Pragmatic Dependency Injection”.

JSR Annotations

Spring also supports the use of the annotations @Inject (JSR-330) and @Resource (JSR-250), which will be described in the “Jakarta EE” sections below. While @Resource is supported out-of-the-box in Spring Boot applications, using @Inject requires the following additional dependency:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

@Value

The @Value annotation can be used to inject application properties:

@Value("${my.property}")
String myProperty;

This annotation can also be applied to method parameters and constructor parameters. Moreover, it supports the Spring Expression Language inside “#{…}” for assembling more complex values:

@Value("#{'${my.property}'.toUpperCase()}")
String myProperty;

@Bean

Even though the @Bean annotation is used for defining injectable beans, methods with this annotation also benefit from dependency injection:

@Bean
MyBean mb(InjectMe injectMe) {
  // "injectMe" is injected and can be used for constructing "MyBean"
  return new MyBean(injectMe);
}

Spring: Disambiguation

@Primary

The simplest way to eliminate ambiguity among injectable beans is to apply the @Primary annotation to the bean that shall win the race:

@Component
class Dog implements Pet { }

@Component
@Primary
class Cat implements Pet { }

In this constellation, a Cat will be injected into the following field:

@Autowired
Pet pet;

@Qualifier

The @Qualifier annotation can be applied to both the bean definition and the injection point, and provided with a String value for choosing the desired bean:

// bean definitions:

@Component
@Qualifier("canine")
class Dog implements Pet { }

@Component
@Qualifier("feline")
class Cat implements Pet { }

// injection point:

@Autowired
@Qualifier("feline")
Pet felinePet;

A more advanced and safer usage of @Qualifier is to apply it to another annotation for creating a custom qualifier:

// custom qualifier definition:

@Qualifier
@Retention(RUNTIME)
@Target({ TYPE, METHOD, FIELD, PARAMETER })
public @interface Feline { }

// bean definition:

@Component
@Feline
class Cat implements Pet { }

// injection point:

@Autowired
@Feline
Pet felinePet;

These custom qualifiers can be further refined with additional attributes (similar to the String value of the @Qualifier annotation).

Bean Name and @Named

Spring also takes into account implicit bean names that can be used instead of explicit qualifiers:

// implicit bean name: "dog"
@Component
class Dog implements Pet { }

// implicit bean name: "cat"
@Component
class Cat implements Pet { }

// variable name matches the implicit bean name:
@Autowired
Pet cat;

The bean name can be defined explicitly with the value attribute of the @Component annotation:

@Component("feline")
class Cat implements Pet { }

// variable name matches the explicit bean name:
@Autowired
Pet feline;

Moreover, the bean name can also be defined using the JSR 330 annotation @Named:

@Component
@Named("feline")
class Cat implements Pet { }

// variable name matches the explicit bean name:
@Autowired
Pet feline;

The same annotation can be used for adjusting the name at the injection point:

// @Named must match the bean name:
@Autowired
@Named("feline")
Pet pet;

Generics

Generics provide a natural mechanism for disambiguation:

interface MyInterface<T> { }

@Component
class MyStringBean implements MyInterface<String> { }

@Component
class MyIntegerBean implements MyInterface<Integer> { }

With the above definitions, the following injections will succeed:

// resolves to MyStringBean:
@Autowired
MyInterface<String> myString;

// resolves to MyIntegerBean:
@Autowired
MyInterface<Integer> myInteger;

Spring: Optional & Multiple Injection

Optional dependencies can be injected by using

either on the field, method, or constructor level:

// not invoked
@Autowired(required = false)
void setPet(FantasyPet schroedinger) { ... }

// invoked with Optional.empty
@Autowired
void setPet(Optional<FantasyPet> schroedinger) { ... }

// invoked with null
@Autowired
void setPet(@Nullable FantasyPet schroedinger) { ... }

However, note that using Optional in setters is generally frowned upon as anti-pattern.

Multiple dependencies can be injected into a Collection, Map, or array of the given type:

@Autowired
List<Pet> pets;

@Autowired
Map<String, Pet> pets;

@Autowired
Pet[] pets;

The keys for the injection point of type Map must be Strings, and will receive the bean names.

All of the above, multi-valued injection points will fail if there is not a single bean that can be injected. This means that if the cardinality of your dependencies is not “1..n”, but “0..n”, you have to make this optionality explicit by using one of the mechanisms described above (“required = false”, Optional, or @Nullable).

Delayed injection is possible with Spring’s ObjectFactory

@Autowired
ObjectFactory<Pet> petFactory;

...

Pet pet = petFactory.getObject();

The call to “getObject()” throws an exception if there is no unique bean for this dependency. The ObjectFactory resembles Jakarta EE’s Provider, which can also be used in Spring projects.

A more powerful solution is Spring’s ObjectProvider (resembles Jakarta EE’s Instance, and extends ObjectFactory), which comes with

  • an iterator, which allows running for-loops over multi-valued dependencies (0..n)
  • a stream() method
  • getters depending on the availability and uniqueness of the dependency
@Autowired
ObjectProvider<Pet> petProvider;

...

// loop over instances:
for (Pet pet : petProvider) {
  ...
}

// instance stream:
petProvider.stream().forEach(...);

// conditional getters:
petProvider.getIfAvailable();
petProvider.getIfUnique();

The following table summarizes the return value of the two conditional getters, depending on the number of provided bean instances:

# of instances getIfAvailable getIfUnique
0 null null
1 the bean the bean
>1 exception null

Spring: Programmatic Injection

Programmatic access to Spring beans is provided by the BeanFactory:

@Autowired
private BeanFactory beanFactory;

...

// retrieve a single object (exception otherwise):
Pet pet = beanFactory.getBean(Pet.class);

// retrieve a provider:
ObjectProvider<Pet> petProvider = beanFactory.getBeanProvider(Pet.class);

Disambiguation is possible either by bean name

// works only with bean name, not with @Qualifier
Pet feline = beanFactory.getBean("feline", Pet.class);

or by @Qualifier with BeanFactoryAnnotationUtils:

// works both with bean name and @Qualifier
BeanFactoryAnnotationUtils.qualifiedBeanOfType(
  beanFactory, Pet.class, "feline");

And finally, a simple form of programmatic access is invoking other @Bean methods inside a @Configuration class:

Bean
Cat cat() {
  return new Cat();
}
	
@Bean
Dog dog() {
  Dog dog = new Dog();
  dog.hunts(cat());
  return dog;
}

JEE: Annotations

@Inject

The most important annotation for injecting dependencies in Jakarta EE is @Inject, which can be applied to the following elements:

// field injection
@Inject
MyBean myBean;
// constructor injection
@Inject
SomeClass(MyBean myBean) { ... }
// method injection
@Inject
void someMethod(MyBean myBean) { ... }

Injection can be performed regardless of the access modifier (i.e., you don’t have to use “public” for injection to work).

Constructors and methods can declare multiple parameters to be provided by injection.

Only one constructor must be annotated with @Inject, and even in the presence of such an annotated constructor, an additional default constructor is required.

@Resource

The @Resource annotation is required for injecting certain (JNDI) resources that are not available to @Inject, such as DataSource and the EJB TimerService:

// field injection
@Resource(name = "java:comp/DefaultDataSource")
DataSource ds;

// method injection
@Resource(name = "java:comp/DefaultDataSource")
void setDataSource(DataSource ds) { ... }

@Produces

Even though the @Produces annotation is used for defining injectable beans, methods with this annotation also benefit from dependency injection:

@Produces
MyBean mb(InjectMe injectMe) { 
  // "injectMe" is injected and can be used for constructing "MyBean"
  return new MyBean(injectMe);
}

Others

There are several injection annotations from other Jakarta EE specifications, such as

EJB’s can also be injected with @Inject. @EJB provides several additional attributes and is only necessary for special use cases. The general trend is a tighter integration of CDI into all Jakarta EE specifications, and to use @Inject where possible.

JEE: Disambiguation

@Vetoed & @Alternative

The simplest way to eliminate ambiguity among injectable beans is to apply the @Vetoed annotation to the beans that shall not be considered for injection:

@Vetoed
class Dog implements Pet { }

class Cat implements Pet { }

A slightly more flexible way is to apply the @Alternative annotation to all the candidate classes:

@Alternative
class Dog implements Pet { }

@Alternative
class Cat implements Pet { }

By default, alternatives are not considered for injection, unless they are explicitly activated in beans.xml:

<beans>
  <alternatives>
    <class>foo.bar.Cat</class>
  </alternatives>
</beans>

@Qualifier

The @Qualifier annotation can be applied to another annotation for creating a custom qualifier:

// custom qualifier definition:

@Qualifier
@Retention(RUNTIME)
@Target({ TYPE, METHOD, FIELD, PARAMETER })
public @interface Feline { }

// bean definition:

@Feline
class Cat implements Pet { }

// injection point:

@Inject
@Feline
Pet felinePet;

In contrast Spring’s @Qualifier, the Jakarta EE @Qualifier does not possess a String value attribute and hence, cannot be applied directly to bean definitions. Custom qualifiers can be further refined with additional attributes. An example for such additional attributes is the built-in @Named qualifier, which can be used both in Spring and Jakarta EE.

In the absence of a qualifier, the built-in @Default qualifier is assumed both for bean definition and injection point. So the following, simple example without explicit qualifiers

// bean definition without explicit qualifier:

class Cat implements Pet { }

// injection point without explicit qualifier:

@Inject
Pet felinePet;

is equivalent to the following example with an explicit @Default qualifier:

// bean definition with @Default made explicit:

@Default
class Cat implements Pet { }

// injection point with @Default made explicit:

@Inject
@Default
Pet felinePet;

The implicit @Default qualifier vanishes as soon as another, explicit qualifier is applied. This is important in order to understand why the Cat will not be injected in the following example:

// bean definition with explicit qualifier:

@Feline
class Cat implements Pet { }

// injection point with implicit @Default qualifier,
// will NOT consider @Feline Cat

@Inject
Pet felinePet;

This injection point searches for a @Default Cat, which cannot be satisfied by a @Feline Cat. To tell an injection point that you don’t care about the exact qualifier, annotate it with the built-in qualifier @Any, which is present on every bean definition regardless of other, explicit qualifiers:

// bean definition with explicit qualifier
// and implicit @Any qualifier:

@Feline
class Cat implements Pet { }

// injection point with explicit @Any qualifier:

@Inject
@Any
Pet felinePet;

@Named

The @Named annotation was already mentioned above in the context of Spring. It is a special qualifier with a String value attribute. When used at an injection point, only beans with a matching @Named qualifier are considered:

@Named("feline")
class Cat implements Pet { }

@Inject
@Named("feline")
Pet felinePet;

@Named is special in that it does not replace the implicit @Default qualifier. Hence, the following definition-injection pair is a match (given that there is no other Pet candidate):

// bean definition with @Named,
// retains the implicit @Default qualifier:

@Named("feline")
class Cat implements Pet { }

// injection point with implicit @Default qualifier:

@Inject
Pet felinePet;

Generics

As in Spring, generics provide a natural mechanism for disambiguation in Jakarta EE:

interface MyInterface<T> { }

class MyStringBean implements MyInterface<String> { }

class MyIntegerBean implements MyInterface<Integer> { }

With the above definitions, the following injections will succeed:

// resolves to MyStringBean:
@Inject
MyInterface<String> myString;

// resolves to MyIntegerBean:
@Inject
MyInterface<Integer> myInteger;

JEE: Optional & Multiple Injection

Delayed injection is possible with a Provider, which resembles Spring’s ObjectFactory:

@Inject
private Provider<Pet> petProvider;

...

Pet pet = petFactory.get();

The call to “get()” throws an exception if there is no unique bean for this dependency.

A more powerful solution is Instance (resembles Spring’s ObjectProvider, and extends Provider) because it comes with

  • an iterator, which allows running for-loops over multi-valued dependencies (0..n)
  • a stream() method
  • methods for checking the availability and uniqueness of the dependency
@Inject
Instance<Pet> petInstances;

...

// loop over instances:
for (Pet pet : petInstances) {
  ...
}

// instance stream:
petInstances.stream().forEach(...);

// boolean check methods:
petInstances.isResolvable();
petInstances.isUnsatisfied();
petInstances.isAmbiguous();

The following table summarizes the return value of the three boolean check methods, depending on the number of provided bean instances:

# of instances isResolvable isUnsatisfied isAmbiguous
0 false true false
1 true false false
>1 false false true

Instance also allows applying additional filters (based on type and qualifier) when invoking its “select()” method, which is most useful in combination with programmatic injection (see next chapter).

JEE: Programmatic Injection

The starting point for programmatic access is the CDI object:

CDI<Object> cdi = CDI.current();

This object implements the Instance interface mentioned above, which provides filtering capabilities through its “select()” methods:

// type filter for retrieving a Cat:
Cat cat = CDI.current().select(Cat.class).get();

// type and annotation filter for retrieving a @Feline Pet:
MyInterface myBean = CDI.current()
  .select(
    Pet.class, 
    new AnnotationLiteral<Feline>() {})
  .get();

References

The examples in this blog post were tested with