1. Overview
In this tutorial, we'll learn how to use the Spring @Import annotation while clarifying how it's different from @ComponentScan.
2. Configuration and Beans
Before understanding the @Import annotation, we need to know what a Spring Bean is and have a basic working knowledge of the @Configuration annotation.
Both topics are out of this tutorial's scope. Still, we can learn about them in our Spring Bean article and in the Spring documentation.
Let's assume that we already have prepared three beans – Bird, Cat, and Dog – each with its own configuration class.
Then, we can provide our context with these Config classes:
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { BirdConfig.class, CatConfig.class, DogConfig.class }) class ConfigUnitTest { @Autowired ApplicationContext context; @Test void givenImportedBeans_whenGettingEach_shallFindIt() { assertThatBeanExists("dog", Dog.class); assertThatBeanExists("cat", Cat.class); assertThatBeanExists("bird", Bird.class); } private void assertThatBeanExists(String beanName, Class<?> beanClass) { Assertions.assertTrue(context.containsBean(beanName)); Assertions.assertNotNull(context.getBean(beanClass)); } }
3. Grouping Configurations with @Import
There's no problem in declaring all the configurations. But imagine the trouble to control dozens of configuration classes within different sources. There should be a better way.
The @Import annotation has a solution, by its capability to group Configuration classes:
@Configuration @Import({ DogConfig.class, CatConfig.class }) class MammalConfiguration { }
Now, we just need to remember the mammals:
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { MammalConfiguration.class }) class ConfigUnitTest { @Autowired ApplicationContext context; @Test void givenImportedBeans_whenGettingEach_shallFindOnlyTheImportedBeans() { assertThatBeanExists("dog", Dog.class); assertThatBeanExists("cat", Cat.class); Assertions.assertFalse(context.containsBean("bird")); } private void assertThatBeanExists(String beanName, Class<?> beanClass) { Assertions.assertTrue(context.containsBean(beanName)); Assertions.assertNotNull(context.getBean(beanClass)); } }
Well, probably we'll forget our Bird soon, so let's do one more group to include all the animal configuration classes:
@Configuration @Import({ MammalConfiguration.class, BirdConfig.class }) class AnimalConfiguration { }
Finally, no one was left behind, and we just need to remember one class:
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { AnimalConfiguration.class }) class AnimalConfigUnitTest { // same test validating that all beans are available in the context }
4. @Import vs @ComponentScan
Before proceeding with @Import examples, let's have a quick stop and compare it to @ComponentScan.
4.1. Similarities
Both annotations can accept any @Component or @Configuration class.
Let's add a new @Component using @Import:
@Configuration @Import(Bug.class) class BugConfig { } @Component(value = "bug") class Bug { }
Now, the Bug bean is available just like any other bean.
4.2. Conceptual Difference
Simply put, we can reach the same result with both annotations. So, is there any difference between them?
To answer this question, let's remember that Spring generally promotes the convention-over-configuration approach.
Making an analogy with our annotations, @ComponentScan is more like convention, while @Import looks like configuration.
4.3. What Happens in Real Applications
Typically, we start our applications using @ComponentScan in a root package so it can find all components for us. If we're using Spring Boot, then @SpringBootApplication already includes @ComponentScan, and we're good to go. This shows the power of convention.
Now, let's imagine that our application is growing a lot. Now we need to deal with beans from all different places, like components, different package structures, and modules built by ourselves and third parties.
In this case, adding everything into the context risks starting conflicts about which bean to use. Besides that, we may get a slow start-up time.
On the other hand, we don't want to write an @Import for each new component because doing so is counterproductive.
Take our animals, for instance. We could indeed hide the imports from the context declaration, but we still need to remember the @Import for each Config class.
4.4. Working Together
We can aim for the best of both worlds. Let's picture that we have a package only for our animals. It could also be a component or module and keep the same idea.
Then we can have one @ComponentScan just for our animal package:
package com.baeldung.importannotation.animal; // imports... @Configuration @ComponentScan public class AnimalScanConfiguration { }
And an @Import to keep control over what we'll add to the context:
package com.baeldung.importannotation.zoo; // imports... @Configuration @Import(AnimalScanConfiguration.class) class ZooApplication { }
Finally, any new bean added to the animal package will be automatically found by our context. And we still have explicit control over the configurations we are using.
5. Conclusion
In this quick tutorial, we learned how to use @Import to organize our configurations.
We also learned that @Import is very similar to @ComponentScan, except for the fact that @Import has an explicit approach while @ComponentScan uses an implicit one.
Also, we looked at possible difficulties controlling our configurations in real applications and how to deal with these by combining both annotations.
As usual, the complete code is available over on GitHub.