1. Overview

Spring, by default, manages beans' lifecycle and arranges their initialization order.

But, we can still customize it based on our needs. We can choose either the SmartLifeCycle interface or the @DependsOn annotation for managing initialization order.

This tutorial explores the @DependsOn annotation and its behavior in case of a missing bean or circular dependency. Or in case of simply needing one bean initialized before another.

2. Maven

First of all, let's import spring-context dependency in our pom.xml file. We should always refer to Maven Central for the latest version of dependencies:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

3. @DependsOn

We should use this annotation for specifying bean dependencies. Spring guarantees that the defined beans will be initialized before attempting an initialization of the current bean.

Let's say we have a FileProcessor which depends on a FileReader and FileWriter. In this case, FileReader and FileWriter should be initialized before the FileProcessor.

4. Configuration

The configuration file is a pure Java class with @Configuration annotation:

@Configuration
@ComponentScan("com.baeldung.dependson")
public class Config {
 
    @Bean
    @DependsOn({"fileReader","fileWriter"})
    public FileProcessor fileProcessor(){
        return new FileProcessor();
    }
    
    @Bean("fileReader")
    public FileReader fileReader() {
        return new FileReader();
    }
    
    @Bean("fileWriter")
    public FileWriter fileWriter() {
        return new FileWriter();
    }   
}

FileProcessor* specifies its dependencies with *@DependsOn. We can also annotate a Component with @DependsOn:

@Component
@DependsOn({"filereader", "fileWriter"})
public class FileProcessor {}

5. Usage

Let us create one class File. Each of the beans updates the text within File. FileReader updates it as read. FileWriter updates it as write and FileProcessor updates the text as processed:

@Test
public void WhenFileProcessorIsCreated_FileTextContains_Processed() {
    FileProcessor processor = context.getBean(FileProcessor.class);
    assertTrue(processor.process().endsWith("processed"));
}

5.1. Missing Dependency

In case of missing dependency, Spring throws a BeanCreationException with a base exception of NoSuchBeanDefinitionException. Read more about NoSuchBeanDefinitionException here.

For example, dummyFileProcessor bean depends on a dummyFileWriter bean. Since dummyFileWriter doesn't exist, it throws BeanCreationException:

@Test(expected=NoSuchBeanDefinitionException.class)
public void whenDependentBeanNotAvailable_ThrowsNosuchBeanDefinitionException(){
    context.getBean("dummyFileProcessor");
}

5.2. Circular Dependency

Also, in this case, it throws BeanCreationException and highlights that the beans have a circular dependency:

@Bean("dummyFileProcessorCircular")
@DependsOn({"dummyFileReaderCircular"})
@Lazy
public FileProcessor dummyFileProcessorCircular() {
    return new FileProcessor(file);
}

Circular dependencies can happen if a bean has an eventual dependency on itself, creating a circle:

Bean1 -> Bean4 -> Bean6 -> Bean1

6. Key Points

Finally, there are few points which we should take care of while using @DependsOn annotation:

  • While using @DependsOn, we must use component-scanning
  • If a DependsOn-annotated class is declared via XML, DependsOn annotation metadata is ignored

7. Conclusion

@DependsOn becomes especially useful when building systems with complex dependency requirements.

It facilitates the Dependency Injection, ensuring that Spring will have handled all of the initialization of those required Beans before loading our dependent class.

As always, the code can be found over on GitHub.