Quarkus Extension - Index Build Item

Quarkus has a very powerful extension system based on the concept of BuildItem. Each BuildItem allows you to configure an extension in a specific way. However, the documentation for these BuildItems is very incomplete. Among the hundreds of BuildItems that exist, I propose to study two of them in this post. Let’s focus on ApplicationIndexBuildItem and CombinedIndexBuildItem.

A Story of Jandex

Quarkus needs to gather information about the application at build time. Jandex allows analyzing and storing metadata of the application’s classes in a jandex.idx file. This file is then used by Quarkus to retrieve information about the classes. This is a significant improvement over using runtime introspection.

Creating a Jandex Index

The simplest way to create a Jandex index is to use the Maven plugin: jandex-maven-plugin.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<build>
    <plugins>
        <plugin>
            <!-- https://github.com/wildfly/jandex-maven-plugin -->
            <groupId>org.jboss.jandex</groupId>
            <artifactId>jandex-maven-plugin</artifactId>
            <version>1.2.3</version>
            <executions>
                <execution>
                    <id>make-index</id>
                    <goals>
                        <!-- phase is 'process-classes' by default -->
                        <goal>jandex</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

The creation of the META-INF/jandex.idx file occurs right after the compilation phase. This prevents the index from being recalculated each time by all the applications using this dependency.

ApplicationIndexBuildItem and CombinedIndexBuildItem

Functionally, these two build items do exactly the same thing: they scan the application. The only difference is that ApplicationIndexBuildItem only scans the application’s classpath, whereas CombinedIndexBuildItem scans both the application’s and its dependencies’ classpath. In practice, CombinedIndexBuildItem is often preferred.

Example

Let’s now see an example of usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@BuildStep()
void indexClassPath(ApplicationIndexBuildItem appIndex, CombinedIndexBuildItem fullIndex) {
    // Application classpath scanning
    appIndex.getIndex().getAnnotations(Foo.class)
            .forEach(annotationInstance -> {
                LOG.info(annotationInstance.target.asClass().name());
            });
    // Application + dependencies classpath scanning
    fullIndex.getIndex().getAnnotations(Foo.class)
            .forEach(annotationInstance -> {
                LOG.info(annotationInstance.target.asClass().name());
            });
}

In this example, all classes annotated with @Foo are discovered and printed to the console. Other methods are also available, such as getKnownClasses(), getAllKnownImplementors(), and getAllKnownSubClasses(), which respectively list all classes, those implementing a specific interface, and those extending a particular class.

It is then possible to provide the found classes to other BuildItems to, for example, add these classes as injectable Beans by Quarkus.

1
2
3
4
5
6
7
8
@BuildStep()
void indexClassPath(CombinedIndexBuildItem fullIndex, BuildProducer<AdditionalBeanBuildItem> producer) {
    // Application + dependencies classpath scanning
    fullIndex.getIndex().getAnnotations(Foo.class)
            .forEach(annotationInstance -> producer.produce(
                    new AdditionalBeanBuildItem(annotationInstance.target().asClass().name().toString()))
            );
}

Conclusion

ApplicationIndexBuildItem and CombinedIndexBuildItem make it easy to retrieve information about the classes present in the classpath, which can then be used in your extension. This scan is performed during the application’s build via Jandex.

0%