In Spring Framework, you can basically use any of the three annotations for Dependency Injection, namely @Autowired, @Resource and @Inject. The @Autowired annotation belongs to the core-spring, however, the other two belongs to the Java extension package @javax.annotation.Resource and @javax.inject.Inject.

We will look into the use of each of these annotations with a practical use case, to help you choose the one that best suits you.

If you are using JDK 9 or higher, please make sure you add the javax.annotation-api dependency for JSR-250 to work.

XML
 <!-- Use the recent version shown below -->
 <dependency>
     <groupId>javax.annotation</groupId>
     <artifactId>javax.annotation-api</artifactId>
     <version>1.3.2</version>
 </dependency>

For later version, 1.3.3 onwards use jakarta.annotation-api instead of javax.annotation-api.

XML
<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>1.3.5</version>
</dependency>

Common example

I will use the same set of classes to understand the Injections using @Resource, @Inject and @Autowired. As you can see the abstract class FileReader is extended by 2 classes, PdfFileReader and WordFileReader. The below example is used to explore the wiring (Dependency Injection) in all the 3 cases.

Java
package basic.ioc.wiring;

public abstract class FileReader {
  public void print() {
    System.out.println("Inside FileReader");
  }
}
Java
package basic.ioc.wiring;
import org.springframework.stereotype.Component;

@Component
public class PdfFileReader extends FileReader {

  @Override
  public void print() {
    System.out.println("Inside PdfFileReader");
  }
}
Java
package basic.ioc.wiring;
import org.springframework.stereotype.Component;

@Component
public class WordFileReader extends FileReader {

  @Override
  public void print() {
    System.out.println("Inside WordFileReader");
  }
}
Java
package basic.ioc.wiring;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ComponentScan("basic.ioc.wiring")
@Configuration
public class ConfigWiring {
  //extra configs goes here
}

The @Configuration annotation in ConfigWiring indicates that this is used as a source of bean definitions. As you rightfully observed, the FileReader is not annotated with @Component as it is an abstract class. There are two concrete classes that extend FileReader, PdfFileReader and WordFileReader. Spring provides stereotype @Component annotation which registers the class as spring managed component.

1. @Resource – Dependency Injection using JSR-250

The @Resource annotation is from JSR-250 specification, which means it is from javax.annotation.Resource. If you are using JDK8+, make sure to add the JSR-250 dependency jar in your maven/Gradle file. For later version, 1.3.3 onwards use jakarta.annotation-api instead of javax.annotation-api.

XML
<!-- JSR 250 -->
<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>1.3.5</version>
</dependency>

The Resource annotation class definition looks as below. As you can see, it accepts two important parameters that are type and name. The @Target({TYPE, FIELD, METHOD}) indicates where the @Resource annotation can be used.

Java
package javax.annotation;

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {

    String name() default "";
    Class<?> type() default java.lang.Object.class;
    boolean shareable() default true;
    String mappedName() default "";
    String description() default "";
    //... Other methods skipped
}
Remember the following when working with @Resource annotation

  1. You can only use @Resource annotation on fields or bean property setters.
  2. In other words, @Resource annotation can never be used to achieve constructor injections.
  3. The precedence that @Resource annotation takes to do dependency injection is:
    • Match by Name
    • Match by Type
    • Match by @Qualifier
  4. The @Resource annotation takes 2 important optional parameter name and type. If no name is explicitly specified, the default name is derived from the field name or setter method.
  5. Similarly, if no type is specified explicitly, it will do a type match and try to resolve it.

What is Dependency Ambiguity

All of the below codes uses common examples from above, where FileReader is an abstract class and there are 2 classes PdfFileReader and WordFileReader that extends FileReader.

When we want to inject a dependency, we can just annotate the property or its setter method with the @Resource annotation. The problem occurs when we have ambiguity. The code below throws NoUniqueBeanDefinitionException as we have 2 classes extending the FileReader.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Show the exception that gets reproduced
  @Resource
  private FileReader fReader;
}
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'basic.ioc.wiring.ResourceTest': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'basic.ioc.wiring.FileReader' available: expected single matching bean but found 2: pdfFileReader,wordFileReader

The above code is the reason why we need to write code in such a way that there is no dependency ambiguity.

1.1. Resolve dependency by Name

For simplicity of understanding, I am using the @Resource annotation on the property, you should use them on setter for performance reasons. In the first example below, the field name pdfFileReader is used as the bean name to resolve the dependency.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Resolve by property name
  @Resource
  private FileReader pdfFileReader;

  @Test
  public void resolveByPropertyName() {
    Assert.assertNotNull(pdfFileReader);
    Assert.assertEquals(pdfFileReader.getClass(), PdfFileReader.class);
  }
}

You can also specify the bean name explicitly as shown below.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Resolve by explicit name
  @Resource(name = "wordFileReader")
  private FileReader reader;

  @Test
  public void resolveByExplicitName() {
    Assert.assertNotNull(reader);
    Assert.assertEquals(reader.getClass(), WordFileReader.class);
  }
}

1.2. Resolve dependency by Type

Spring will try to resolve the dependency by name. However, it will not find any bean with this name so next, it tries to resolve by type. As we have WordFileReader bean already registered, spring will autodetect this type and resolve this field.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {
 
 //Resolve by type auto detection
  @Resource
  private WordFileReader fileReader;

  @Test
  public void resolveByAutoType(){
    Assert.assertNotNull(fileReader);
    Assert.assertEquals(fileReader.getClass(), WordFileReader.class);
  }
}

In the below code, Spring can neither detect the dependency by name nor resolve by type. Because there is ambiguity, type detection will fail with Exception BeanCreationException. So we will specify the type explicitly, type = PdfFileReader.class to inject PdfFileReader.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Resolve by explicit Type
  @Resource(type = PdfFileReader.class)
  private FileReader fileReader2;

  @Test
  public void resolveByExplicitType() {
    Assert.assertNotNull(fileReader2);
    Assert.assertEquals(fileReader2.getClass(), PdfFileReader.class);
  }
}

1.3. Resolve dependency by @Qualifier

Spring has this special @Qualifier annotation in which we can pass the name of the bean to be resolved.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class ResourceTest {

  //Resolve by Qualifier
  @Qualifier("pdfFileReader")
  @Resource
  private FileReader myFileReader;
  
  @Test
  public void resolveByQualifier(){
    Assert.assertNotNull(myFileReader);
    Assert.assertEquals(myFileReader.getClass(), PdfFileReader.class);
  }
}

2. @Inject – Dependency Injection using JSR-330

The @Inject annotation is from jakarta.inject.Inject. If you are using JDK8+, make sure to add the JSR-330 dependency jar in your pom/Gradle file.

XML
<!-- https://mvnrepository.com/artifact/jakarta.inject/jakarta.inject-api -->
<dependency>
    <groupId>jakarta.inject</groupId>
    <artifactId>jakarta.inject-api</artifactId>
    <version>2.0.1</version>
</dependency>

The Inject annotation looks as follow, which means it can be used on the field, setter or constructor based annotations.

Java
package javax.inject;
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}
Remember the following when working with @Inject annotation:

  1. The @Inject can be used on Setter, Field, or Constructor to do the respective type injections.
  2. The precedence that this annotation takes is:
    • Resolve by Type
    • Resolve by Qualifier@Qualifier annotation
    • Resolve by Name@Named annotation
  3. The Inject annotation does not take any parameter.

1. Resolve dependency by Type

In the below examples, I have used the annotation on the property fields. But in a real project, you should use it on setters or constructors due to performance reasons.

The example below fileReader is of the type PdfFileReader, so the dependency injection is straight forward.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class InjectTest {

  //Inject by Type
  @Inject
  private PdfFileReader fileReader;

  @Test
  public void injectByType() {
    Assert.assertNotNull(fileReader);
    Assert.assertEquals(fileReader.getClass(), PdfFileReader.class);
  }
}

2. Resolve by @Qualifier annotation

There are 2 implementations of the base class FileReader and they are as PdfFileReader and WordFileReader. To get rid of this ambiguity, you need to specify the bean name in @Qualifier annotation. The code @Qualifier("wordFileReader") below represents the same.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class InjectTest {

  //Inject by @Qualifier
  @Inject
  @Qualifier("wordFileReader")
  private FileReader fileReader3;
  
  @Test
  public void injectByQualifier() {
    Assert.assertNotNull(fileReader3);
    Assert.assertEquals(fileReader3.getClass(), WordFileReader.class);
  }
}

If you don’t specify the @Qualifier, you will end up reproducing the exception as below.

SEVERE: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@783a467b] to prepare test instance [basic.ioc.wiring.InjectTest@5b202a3a]
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'basic.ioc.wiring.InjectTest': Unsatisfied dependency expressed through field 'fileReader3'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'basic.ioc.wiring.FileReader' available: expected single matching bean but found 2: pdfFileReader,wordFileReader

3. Resolve dependency by Name

The @Inject annotation detects the bean to be injected in 2 ways. First, if @Named("beanName") annotation is specified. Otherwise, the field name is used as the bean name. The @Named annotation is from JSR-330 javax.inject.* package and it is used to specify the bean name whose instance will be injected.

The line-6 represents the bean name detection using @Named annotation. On the other hand, line-17 represents the auto-detection by field name.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class InjectTest {

  //Inject by name with @Named annotation
  @Inject
  @Named("pdfFileReader")
  private FileReader fileReader2;

  @Test
  public void injectByName() {
    Assert.assertNotNull(fileReader2);
    Assert.assertEquals(fileReader2.getClass(), PdfFileReader.class);
  }

  //Inject by field Name
  @Inject
  private FileReader wordFileReader;

  @Test
  public void injectByName2() {
    Assert.assertNotNull(wordFileReader);
    Assert.assertEquals(wordFileReader.getClass(), WordFileReader.class);
  }
}

3. @Autowired – Dependency Injection in Spring style

The @Autowired annotation is similar to @Inject annotation. The only difference is @Inject is from JSR-330 specification, and the @Autowired is purely from the Spring framework.

The @Autowired annotation looks as below. As you can observe, @Autowired can be used on the constructor, setter, field and in the parameters as well.

Java
package org.springframework.beans.factory.annotation;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, 
ElementType.PARAMETER, ElementType.FIELD, 
ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;
}

The examples discussed below uses field-based dependency injection, but you should always use the setter based or constructor based injections in your application due to performance reasons.

Remember the following when working with @Autowired annotation:

  1. The @Autowired can be used on Setter, Field, Constructor or on parameters to do the respective type injections.
  2. The precedence that this annotation takes is:
    • Resolve by Type
    • Resolve by Qualifier – using the @Qualifier
    • Resolve by Name
  3. The @Autowired(required=true) annotation takes required as an optional parameter which is always true by default.

1. Resolve dependency by Type

Spring first tries to detect the bean to be injected by its type and do the DI. In the code below, spring simply injects PdfFileReader instance.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class AutowiredTest {

  //Inject by Type
  @Autowired
  private PdfFileReader pdfFileReader;

  @Test
  public void injectByType() {
    Assert.assertNotNull(pdfFileReader);
    Assert.assertEquals(pdfFileReader.getClass(), PdfFileReader.class);
  }
}

2. Resolve dependency by @Qualifier

When you have ambiguity, use the @Qualifier annotation to specify the bean name to be injected.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class AutowiredTest {

  //Inject by @Qualifier
  @Autowired
  @Qualifier("wordFileReader")
  private FileReader fileReader;

  @Test
  public void injectByQualifier() {
    Assert.assertNotNull(fileReader);
    Assert.assertEquals(fileReader.getClass(), WordFileReader.class);
  }
}

3. Resolve dependency by Name

Spring @Autowired annotation can resolve the dependency by field name as discussed before. In this example, spring inject the bean with name wordFileReader.

Java
@SpringJUnitConfig(ConfigWiring.class)
public class AutowiredTest {

  //Inject by Field name
  @Autowired
  private FileReader wordFileReader;

  @Test
  public void injectByFieldName() {
    Assert.assertNotNull(wordFileReader);
    Assert.assertEquals(wordFileReader.getClass(), WordFileReader.class);
  }
}

Conclusion:

This article only focuses on the auto wiring part. I prefer using @Autowired or @Inject. If you want to understand the dependency injection and scopes etc in Spring, check out the respective articles.

The complete code example is available on GitHub, download the code and try it out. The test cases are located here and the common classes are here.


By |Last Updated: April 2nd, 2024|Categories: Spring Framework|