Spring Framework supports the injection of the Java Collection types List, Set, Map and Properties. You can use XML as well as annotations based configurations. We will learn Constructor, Setter, and Field injections for the collections using annotations based configurations in this article. The complete code example is available in the GitHub code repository.

I recommend understanding the basics of Dependency Injection in Spring if you are new to Spring.

Collections injection using @Autowired

All we need is annotate the Collections like List, Set, Map with @Autowired annotation. Alternatively, you can use @Inject annotation as well. We will explored the injections through Constructor, Setters, and Field.

  1. Constructor Injection for Collections (List, Set, Map)
  2. Setter Injection for Collections (List, Set, Map)
  3. Field Injection of Collections using @Autowired

1. Constructor Injection for Collections (List, Set, Map)

The Java collections Constructor Injection is done using the @Autowired annotation. Point to note is, the Spring IoC should be able to find the beans with the respective types to inject. The respective collection types are configured in CollectionsConfig.java.

The constructor is annotated with @Autowired, indicating that Spring should inject dependencies into this constructor when creating an instance of the class. The constructor parameters are of types List<String>, Set<Long>, and Map<Long, String>, corresponding to the dependencies to be injected.

ConstructorInjection.java
@Component
public class ConstructorInjection {

  private List<String> names;
  private Set<Long> phones;
  private Map<Long, String> phoneNameMap;
  
  @Autowired
  public ConstructorInjection(List<String> names, Set<Long> phones,
                              Map<Long, String> phoneNameMap) {
    this.names = names;
    this.phones = phones;
    this.phoneNameMap = phoneNameMap;
  }
  
//Getter, Setter, toString() omitted for bravity
}

In the provided CollectionsConfig class, we have configuration methods for creating beans of different collection types. Let’s understand how it works:

CollectionsConfig.java
@Configuration
@ComponentScan("basic.ioc.bean.collections")
public class CollectionsConfig {

  @Bean
  public List<String> namesList() {
    List names = new ArrayList<String>();
    names.add("Salman Khan");
    names.add("Hrithik Roshan");
    return names;
  }
  
  @Bean
  public Set<Long> numbersBean() {
    Set<Long> numbers = new HashSet<>();
    numbers.add(2222222222L);
    numbers.add(1212121212L);
    return numbers;
  }
  
  @Bean
  public Map<Long, String> phoneNameMapBean() {
    Map<Long, String> phoneNames = new HashMap<>();
    phoneNames.put(888888888888L, "Ram");
    phoneNames.put(777777777777L, "Bhim");
    return phoneNames;
  }
}
  1. @Configuration Annotation: This annotation marks the class as a configuration class, indicating that it contains bean definitions.
  2. @ComponentScan Annotation: This annotation specifies the package(s) to scan for Spring-managed components. In this case, it scans the package "basic.ioc.bean.collections" for components.
  3. Bean Definition Methods:
    • namesList() Method: This method defines a bean of type List<String>. It creates an ArrayList of names, adds some elements to it, and returns the list.
    • numbersBean() Method: This method defines a bean of type Set<Long>. It creates a HashSet of phone numbers, adds some elements to it, and returns the set.
    • phoneNameMapBean() Method: This method defines a bean of type Map<Long, String>. It creates a HashMap of phone numbers mapped to names, adds some entries to it, and returns the map.
  4. @Bean Annotation: This annotation marks methods that produce bean instances within the Spring container. Each method annotated with @Bean defines a bean with a unique name based on the method name.
  5. Bean Naming: The names of the beans are inferred from the method names (namesList, numbersBean, phoneNameMapBean).
  6. Bean Initialization: When the Spring application context is initialized, it scans the configuration class, identifies the @Bean methods, invokes them, and registers the returned objects as beans in the context.
Java
@Test
public void testCollectionsMapping(){

    ApplicationContext aContext
        = new AnnotationConfigApplicationContext(CollectionsConfig.class);
        
    ConstructorInjection cInjection = aContext.getBean(ConstructorInjection.class);
    System.out.println(cInjection);
}

Output:

ConstructorInjection[names=[Salman Khan, Hrithik Roshan], phones=[1212121212, 2222222222], phoneNameMap={888888888888=Ram, 777777777777=Bhim}]

Understand the bean configurations in CollectionsConfig.java, the method namesList() returns List, numbersBean() returns Set and the phoneNameMapBean() returns the Map. These are then @Autowired in ConstructorInjection.java.

2. Setter Injection for Collections (List, Set, Map)

All you have to do is place the @Autowired annotations on setters and make sure the respective bean types are configured. In this case, we will use the same configurations from the previous example CollectionsConfig.java. In the provided SetterInjection class, we have three setter methods annotated with @Autowired, each responsible for setting a specific collection dependency (names, phones, phoneNameMap).

SetterInjection.java
@Component
public class SetterInjection {

  private List<String> names;
  private Set<Long> phones;
  private Map<Long, String> phoneNameMap;
  
  @Autowired
  public void setNames(List<String> names) {
    this.names = names;
  }
  
  @Autowired
  public void setPhones(Set<Long> phones) {
    this.phones = phones;
  }
  
  @Autowired
  public void setPhoneNameMap(Map<Long, String> phoneNameMap) {
    this.phoneNameMap = phoneNameMap;
  }
  
  //toString() omitted for bravity.
}

In the above SetterInjection class:

  1. Dependencies: The class has three fields: names, phones, and phoneNameMap, representing a list of names, a set of phone numbers, and a map of phone numbers to names, respectively.
  2. Setter Methods: Each setter method (setNames, setPhones, setPhoneNameMap) is annotated with @Autowired, indicating that Spring should inject dependencies into these methods.
  3. Injection: When an instance of SetterInjection is created by the Spring container, it automatically resolves the dependencies for the setter methods. If Spring finds beans of type List<String>, Set<Long>, and Map<Long, String> registered in its context, it injects them into the corresponding setter methods.
  4. Usage: After the injection process, the setter methods are called, setting the dependencies in the class, allowing them to be accessed and used as needed.

3. Field Injection for Collections using @Autowired

To make the Spring IoC do field injection, place @Autowired annotations on the fields as shown below. And obviously, you need to configure the beans to be injected in these collections fields like CollectionsConfig.java from the first example.

It is not recommended to use the field-based injection as it has a performance impact. Use either the constructor based injection or Setter based injection.

FieldInjection.java
@Component
public class FieldInjection {

  @Autowired //Avoid using property injection
  private List<String> names;
  
  @Autowired //Avoid using property injection
  private Set<Long> phones;
  
  @Autowired //Avoid using property injection
  private Map<Long, String> phoneNameMap;
}

Injecting the Bean references in Collections

In the previous example, you saw List, Set, and Map with wrapper classes. In the real-world, you need to inject/map collections of a user-defined bean.

The below example shows the injection of List<JbdBean> in the BeanreferenceCollections class. It is important to learn the configurations. You can see in the output below, there are 2 JbdBean inside the list. This is because, there are 2 Jbdbean configured in CollectionsConfig. This illustrates a common scenario in real-world Spring applications where collections of custom beans need to be injected and managed. . It contains a field list representing a list of JbdBean instances. The setList method is annotated with @Autowired, allowing Spring to inject the list of JbdBean instances into this field.

BeanReferenceCollections.java
@Component
public class BeanReferenceCollections {

  private List<JbdBean> list;

  @Autowired
  public void setList(List<JbdBean> list) {
    this.list = list;
  }

  @Override
  public String toString() {
    return new StringJoiner(", ", BeanReferenceCollections.class.getSimpleName() + "[", "]")
        .add("list=" + list)
        .toString();
  }
}

JbdBean is a simple POJO class representing a user-defined bean. It has a field uuid and a setter method setUuid to set the value of uuid. The toString method is overridden to provide a meaningful string representation of the bean.

JbdBean.java
public class JbdBean {
  private String uuid;

  public void setUuid(String uuid) {
    this.uuid = uuid;
  }

  @Override
  public String toString() {
    return new StringJoiner(", ", JbdBean.class.getSimpleName() + "[", "]")
        .add("uuid='" + uuid + "'")
        .toString();
  }
}

The CollectionsConfig contains bean definition methods for creating instances of JbdBean. Two beans (jbdBean1 and jbdBean2) are defined, each creating a new instance of JbdBean with a unique UUID.

CollectionsConfig.java
@Configuration
@ComponentScan("basic.ioc.bean.collections")
public class CollectionsConfig {

  @Bean
  public JbdBean jbdBean1(){
    JbdBean bean = new JbdBean();
    bean.setUuid(UUID.randomUUID().toString());
    return bean;
  }

  @Bean
  public JbdBean jbdBean2(){
    JbdBean bean = new JbdBean();
    bean.setUuid(UUID.randomUUID().toString());
    return bean;
  }
}
Java
  @Test
  public void testBeanReferenceCollections(){
  
    ApplicationContext aContext
        = new AnnotationConfigApplicationContext(CollectionsConfig.class);
    BeanReferenceCollections beanReferCollections = aContext.getBean(BeanReferenceCollections.class);

    System.out.println(beanReferCollections);
  }

Output:

BeanReferenceCollections[list=[JbdBean[uuid='b3f5297c-112c-47de-bfd9-47ee4a23e35c'], JbdBean[uuid='85abf89c-ebc1-4ae3-8032-927636d3e6fa']]]

Inject the Sorted beans in Collections using @Order

In the provided example, we demonstrate how to inject sorted beans into a collection (List<NameBean>) using the @Order annotation in Spring Framework. The @Order annotation specifies the order in which beans should be injected, resulting in a sorted collection based on the specified order.

CollectionsConfig.java: This is a configuration class annotated with @Configuration, responsible for defining bean instances of NameBean. Three beans (name1, name2, name3) are defined, each creating a new instance of NameBean with a specified name. Each bean is annotated with @Order, specifying the order in which they should be injected into the collection.

CollectionsConfig.java
@Configuration
@ComponentScan("basic.ioc.bean.collections")
public class CollectionsConfig {

  @Bean @Order(3)
  public NameBean name1(){
    return new NameBean("John");
  }

  @Bean @Order(1)
  public NameBean name2(){
    return new NameBean("Ram");
  }

  @Bean @Order(2)
  public NameBean name3(){
    return new NameBean("Shyam");
  }
}

NameBean.java: This is a simple POJO class representing a bean with a single field name. It has a constructor to set the value of name and an overridden toString method to provide a meaningful string representation of the bean.

NameBean.java
public class NameBean {
  private String name;
  public NameBean(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return new StringJoiner(", ", NameBean.class.getSimpleName() + "[", "]")
        .add("name='" + name + "'")
        .toString();
  }
}

SortedNamesCollection.java: This class is annotated with @Component, indicating that it is a Spring-managed component. It contains a field names representing a list of NameBean instances. The setNames method is annotated with @Autowired, allowing Spring to inject the list of NameBean instances into this field.

SortedNamesCollection
@Component
public class SortedNamesCollection {
  private List<NameBean> names;

  @Autowired
  public void setNames(List<NameBean> names) {
    this.names = names;
  }

  @Override
  public String toString() {
    return new StringJoiner(", ", SortedNamesCollection.class.getSimpleName() + "[", "]")
        .add("names=" + names)
        .toString();
  }
}
JUnit Test
  @Test
  public void testSortedCollection(){
  
    ApplicationContext aContext
        = new AnnotationConfigApplicationContext(CollectionsConfig.class);
    SortedNamesCollection collection = aContext.getBean(SortedNamesCollection.class);
  
    System.out.println(collection);
  }

Output:

SortedNamesCollection[names=[NameBean[name='Ram'], NameBean[name='Shyam'], NameBean[name='John']]]

Setting a default value for Collections using SpEL

SpEL stands for Spring Expression Language. It is a powerful expression language that allows programmers to query and manipulate objects at runtime. Using this we will assign default values to Collections.

Below is a simple example that shows the use of SpEL to assign an empty list value.

CollectionsBean.java
public class CollectionsBean {

  @Value("${names.list:}#{T(java.util.Collections).emptyList()}")
  private List<String> namesList;
  
}

In this article, we delved into the intricacies of mapping and injecting collections in Spring applications. We explored how to define beans for collections such as lists, sets, and maps using Java configuration classes annotated with @Configuration. By leveraging the @Bean annotation, we defined methods that produce instances of these collection types within the Spring container.

Furthermore, we examined two primary methods of injecting these collections into Spring-managed components: constructor injection and setter injection. With constructor injection, dependencies are provided through a class constructor, while setter injection involves using setter methods to set the dependencies.

Throughout the article, we emphasized the flexibility and modularity afforded by Spring’s dependency injection mechanisms. By properly configuring and injecting collections, developers can ensure cleaner, more maintainable code that is easier to test and extend.

Additionally, we touched upon the use of @Qualifier annotations to select specific beans for injections when multiple beans of the same type are available. This annotation allows developers to specify the name or ID of the desired bean, ensuring precise dependency resolution.


By |Last Updated: April 1st, 2024|Categories: Spring Framework|

Table of Contents