1. Overview

The @Import annotation is the primary mechanism to import @Bean definitions typically contained in @Configuration classes. Although it is mainly used to import configuration classes, its usage isn't limited to that. In this tutorial, we'll examine different examples to import @Bean definitions contained in @Configuration, @Component, and ImportSelector classes.

2. Sample Application

Let's start with the sample application.

We have the Counter class:

public class Counter {

    private int current = 0;

    public void count() {
        System.out.println(current++);
    }
}

Then the ImpressionService class uses Counter:

public class ImpressionService {

    private final Counter counter;

    public ImpressionService(Counter counter) {
        this.counter = counter;
    }

    public void countImpression(){
        counter.count();
    }
}

3. Use @Import with @Configuration

We'll first look at how we can import @Configuration classes using @Import. @Configuration classes provide bean definitions through either @Bean methods or component scanning:

@Configuration
public class CounterConfiguration {

    @Bean
    public Counter counter() {
        return new Counter();
    }
}

Here, we have the CounterConfiguration class. It's providing a bean definition for Counter.

When another @Configuration class imports CounterConfiguration, the Counter bean becomes available to it:

@Configuration
@Import(CounterConfiguration.class)
public class MainConfiguration {

    @Bean
    public ImpressionService impressionService(Counter counter) {
        return new ImpressionService(counter);
    }
}

In this example, we have the MainConfiguration class. Note that the impressionService method declares Counter as a method parameter. This is valid because MainConfiguration imports the CounterConfiguration class where Counter is exposed as a bean.

4. Use @Import with @Component

Although we generally import @Configuration classes, it is also valid to import @Component classes using @Import. Remember that @Component classes can also provide bean definitions using the lite @Bean methods:

For this purpose, we'll change our ImpressionService class a bit:

@Component
public class ImpressionService {

    private final Counter counter;

    public ImpressionService(Counter counter) {
        this.counter = counter;
    }

    ...

    @Bean
    public static Counter counter() {
        return new Counter();
    }
}

In this modified version of ImpressionService, we're defining a Counter bean using the counter method. Notice that we're declaring the @Bean method as static so that Spring can create the bean without needing to initialize ImpressionService.

Then another @Configuration class can import the ImpressionService class:

@Configuration
@Import(ImpressionService.class)
public class MainConfiguration {
}

When MainConfiguration imports ImpressionService, it loads two bean definitions, ImpressionService and Counter.

5. Use @Import with ImportSelector

Next, we'll look at how we can use ImportSelector to control the import process in a fine-grained manner. For example, we can use ImportSelector to select one @Configuration class over another according to some criteria:

Firstly, we'll refactor a bit and add the environment information to Counter:

public class Counter {

    private final String environment;
    private int current = 0;

    public Counter(String environment) {
        this.environment = environment;
    }

    public void count() {
        System.out.println(environment + ": " + current++);
    }
}

Then we'll write two @Configuration classes for local and prod:

@Configuration
public class LocalCounterConfiguration {

    @Bean
    public Counter counter() {
        return new Counter("local");
    }
}

@Configuration
public class ProdCounterConfiguration {

    @Bean
    public Counter counter() {
        return new Counter("prod");
    }
}

Now that we have two configurations, we want to use LocalCounterConfiguration only on the local environment:

public class CounterImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        if (isOnLocal()) {
            return new String[]{LocalCounterConfiguration.class.getName()};
        }

        return new String[]{ProdCounterConfiguration.class.getName()};
    }

    private boolean isOnLocal() {
        // return after environment check...
    }
}

Here, the CounterImportSelector class returns either LocalConfiguration or ProdConfiguration according to the environment.

Similar to the previous examples, we'll import our ImportSelector implementation using the @Import annotation:

@Configuration
@Import(CounterImportSelector.class)
public class MainConfiguration {

    @Bean
    public ImpressionService impressionService(Counter counter) {
        return new ImpressionService(counter);
    }
}

6. Meta-Annotation with @Import

Lastly, we'll examine how we can create a meta-annotation using @Import. The newly created annotation serves similar to @Import but tells more about the intention. Spring itself makes use of these meta-annotations in the format of @EnableSomething. For example, Spring provides the @EnableAsync annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
...
}

Similar to this annotation, we'll create a new one to import CounterConfiguration:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(CounterConfiguration.class)
public @interface EnableCounter {
}

Here, we have the @EnableCounter annotation following the EnableSomething naming format. Note that we're meta-annotating it with @Import(CounterConfiguration.class).

Then another configuration uses it in place of @Import:

@Configuration
@EnableCounter
public class MainConfiguration {
...
}

7. Summary

In this tutorial, we've examined different usages of the @Import annotation. We started with importing the configuration classes. Then we showed that we can also import @Component classes and ImportSelector implementations. Lastly, we detailed how we can create a meta-annotation.

As always, the source code for all examples in this tutorial is available on Github.