1. Introduction

Spring provides classpath scanning for finding annotated components under the given packages. Although this process is fairly fast, it may slow down the startup for large applications on slow IO environments. In this tutorial, we'll examine how we can generate an index of candidate components at compile-time so that Spring uses this index to create beans.

2. Maven Dependency

Firstly, we'll add the spring-context-indexer Maven dependency:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-indexer</artifactId>
    <version>5.2.5.RELEASE</version>
    <optional>true</optional>
</dependency>

3. What is the Candidate Component Index?

After adding the required dependency, there is no other action required on our side. When we compile the project, Spring generates the META-INF/spring.components file. This file is our index that contains the candidate components:

com.javabyexamples.spring.core.beanindexing.indexedbased.SampleComponent1=org.springframework.stereotype.Component
com.javabyexamples.spring.core.beanindexing.indexedbased.SampleRepository1=org.springframework.stereotype.Component
com.javabyexamples.spring.core.beanindexing.javaxbased.NamedService=javax.inject.Named
...

Here, we have some key-value pairs. The key is the fully qualified class name of the candidate component. The value, on the other hand, is the annotation that qualified the class a candidate. For example, Spring adds SampleComponent1 to the index because it has the @Component annotation. Similarly, the index contains the NamedService class because it has the @java.inject.Named annotation. In a moment, we'll detail the rules for inclusion into the index.

This index file - META-INF/spring.components - is also included in the jar file. So when the Spring application starts, if it finds the index file, it uses this static list of components and skips classpath scanning.

A sample run shows the index usage:

DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Using candidate component class from index: 
com.javabyexamples.spring.core.beanindexing.custom.CustomComponent1
DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Using candidate component class from index: 
com.javabyexamples.spring.core.beanindexing.javaxbased.NamedService
...

One important note is that if we want to use component indexing, all modules of our application must have an index file. Otherwise, some Spring components will go unnoticed since they aren't listed in an index.

4. Component Index Rules

Now that we've learned the general behavior, we'll next look at the rules of candidate component index.

4.1. @Indexed Stereotype Annotations

Primarily, Spring finds the classes annotated with the @Indexed annotation and adds to the index. Spring also honors the annotations that are meta-annotated with @Indexed. For example, @Component is meta-annotated with this annotation:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component { }

So every class annotated with @Component becomes a candidate and is added to the component index. This behavior is also valid for other stereotype annotations that are meta-annotated with @Component:

@Component
public class SampleComponent1 { }
 
@Repository
public class SampleRepository1 { }
 
@Service
public class SampleService1 { }

Here, we have three classes annotated with @Component, @Repository, and @Service.

When we compile our application, the resulting index file contains these three classes:

com.javabyexamples.spring.core.beanindexing.indexedbased.SampleComponent1=org.springframework.stereotype.Component
com.javabyexamples.spring.core.beanindexing.indexedbased.SampleRepository1=org.springframework.stereotype.Component
com.javabyexamples.spring.core.beanindexing.indexedbased.SampleService1=org.springframework.stereotype.Component

Here, we have SampleComponent1 mapped with @org.springframework.stereotype.Component, since @Component is directly annotated with @Indexed. On the other hand, SampleRepository1 isn't indexed with @Repository but with @Component. This is because @Repository isn't directly meta-annotated with @Indexed. Similarly, SampleService1 is indexed with @Component.

4.2. javax Annotations

Spring also finds the classes that are annotated with a javax.* annotation and adds them to the candidate component index.

For example, it can find the JPA entity classes using @javax.persistence.Entity. Or it can find the components defined with @javax.inject.Named:

@Entity
public class SampleEntity1 { }

@Named
public class NamedService { }

Here, we have an @Entity class and a @Named class.

Each entry in the index file references the related javax annotation:

com.javabyexamples.spring.core.beanindexing.javaxbased.NamedService=javax.inject.Named
com.javabyexamples.spring.core.beanindexing.javaxbased.SampleEntity1=javax.persistence.Entity

Note that in the case of @Entity, Spring doesn't create a bean for SampleEntity1. But nonetheless, it is subject to classpath scanning and thus added to the candidate component index.

4.3. Custom Annotations

Next, we'll create a custom annotation to mark classes as candidate components. For this purpose, we'll meta-annotate our custom annotation with @Indexed:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface CustomIndexed {
}

Here, we have our custom annotation, @CustomIndexed. Thus, when we annotate a class with @CustomIndexed, Spring adds it to the index file:

@CustomIndexed
public class CustomIndexed1 {
}

As expected, the generated index file contains the CustomIndexed1 class:

com.javabyexamples.spring.core.beanindexing.custom.CustomIndexed1=com.javabyexamples.spring.core.beanindexing.custom.CustomIndexed

4.4. Multiple Annotations

Finally, we'll look at how Spring handles the candidate components that have multiple annotations:

@Component
@CustomIndexed
@Named
public class MultiComponent1 {
}

Here, we annotated the MultiComponent1 class with @Component, @Named, and @CustomIndexed. 

When we compile our application, the resulting spring.components file contains:

com.javabyexamples.spring.core.beanindexing.custom.CustomComponent1=org.springframework.stereotype.Component,
com.javabyexamples.spring.core.beanindexing.custom.CustomIndexed,javax.inject.Named

As we can see, CustomComponent1 is indexed with all three annotations. This shows that an index entry can reference multiple annotations.

5. Disable Candidate Component Index

As mentioned before, in order to use indexing, all modules composing an application must have a candidate component index. If this isn't the case, we can fall back to regular classpath scanning by setting spring.index.ignore to true. We can define this property either as a system property or in spring.properties file at the root of the classpath:

spring.index.ignore=true

6. Summary

In this tutorial, we've examined the usage of the candidate component index. Firstly, we looked at how we can enable the index and how Spring uses it. Then we investigated the rules related to the inclusion into the index.

Finally, check out the source code for all examples in this tutorial over on Github.