Jandex is a space efficient Java class file indexer and offline reflection library. For many years, it lived in the WildFly GitHub organization, as it was originally written for WildFly (actually for JBoss AS 7, WildFly’s predecessor). Recently, Quarkus accelerated Jandex usage outside of WildFly, prompting for higher pace of bug fixes and new features, and we felt that WildFly is no longer the best home for Jandex. With a hint of sadness, we bid farewell to WildFly (you’ve been the best for long!) and are proud to announce that we settle comfortably in the SmallRye space.
SmallRye started as a set of implementations of MicroProfile specifications that could be shared among various runtimes, but it has long been way more than that. Importantly, both WildFly and Quarkus use SmallRye components, so Jandex is certainly no stranger here. To highlight that, without further ado, we announce the first release of Jandex in SmallRye, version 3.0.0!
Jandex 3.0.0 contains many bug fixes and features and, given the major version bump, also some breaking changes. But fear not — upgrading is still pretty easy. The full list of changes can be found in the Jandex issue tracker (which, by the way, moved to GitHub Issues a while ago). Here, we’ll highlight all the important ones and describe how to migrate.
Note that the following text is pretty long and detailed. If you want a TLDR, feel free to skip to the summary.
Move to SmallRye
As mentioned, Jandex is now a SmallRye project.
As part of that, we’ve consolidated all the Jandex projects into a single repository: https://github.com/smallrye/jandex/.
This means that the Jandex Maven plugin is released together with Jandex core, and if you contribute, you no longer have to add test classes to a dedicated
This also entails a change in Maven coordinates.
|Old coordinates||New coordinates|
Recommendation: if you use the Maven Enforcer plugin, configure it to ban any dependencies on
Similar plugin exists for Gradle.
Move to Java 8
Jandex 2.x is a Java 6 project. That has never been a problem, but Java 6 is just too old these days. However, Jandex has always been and continues to be conservative. Even though the major Jandex consumers we know of require Java 11 now, Jandex only bumps to Java 8.
Nothing changes on the ability to parse new Java bytecode. Jandex running on Java 8 can still parse Java 17 bytecode just fine.
Recommendation: if you are not on Java 8+ yet, you really should be.
Uniform annotation access
Accessing annotations has been the original primary use case for Jandex.
However, it has never been very uniform.
ClassInfo type has different methods to access annotations than
AnnotationTarget interface, which all "annotated things" implement, has no annotation-related methods.
With Jandex 3.0.0, this changes.
AnnotationTarget interface gains a slew of method for accessing annotations.
There are 2 kinds of them, actually:
methods that access annotations directly on the target;
methods that access annotations on the target and all nested annotation targets.
The 2nd is how Jandex used to always operate, but the 1st is often what you need. Now, both options are available.
|Purpose||Directly on the target||On the target and nested targets|
Has annotation of given type?
Get single annotation of given type
Get all annotations of given type (not repeatable)
doesn’t make sense, there may only be one
Get all annotations of given type, including repeatable
Get all annotations
BREAKING CHANGE: there used to be a
ClassInfo.annotations() method returning a
That method is renamed to
ClassInfo.annotations() method returns a
Recommendation: if you call the
annotations() method on
ClassInfo, move to
Note that callers of this method compiled against Jandex 2.x will continue running correctly, because Jandex 3.0.0 includes a synthetic bridge method for binary compatibility.
classAnnotations() methods on
ClassInfo are now deprecated, with the corresponding
declaredAnnotation* methods being the suggested replacement.
Recommendation: if you call any of the
classAnnotation* methods on
ClassInfo, move to
Recommendation: if you call the existing methods that access annotations both on the target and nested annotation targets, and then filter them out to retain only the annotations declared directly on the target, use the
Uniform method parameter access
Jandex uses the
MethodInfo class to represent methods and
MethodParameterInfo to represent method parameters.
MethodInfo used to never expose any access to
You could only access parameters from
MethodInfo through the
parameters() methods, where the
parameterName() returned the name of given parameter, while
parameters() returned the list of parameter types.
MethodParameterInfo class was only used to represent a target of
This is not very convenient.
In Jandex 3.0.0,
MethodInfo has several methods to access parameters:
parametersCount(): returns the number of parameters
parameterTypes(): returns the list of parameter types
parameterName(int): returns the name of given parameter
parameterType(int): returns the type of given parameter
parameters(): returns the list of parameters as
BREAKING CHANGE: there used to be a
MethodInfo.parameters() method returning a
That method is renamed to
MethodInfo.parameters() method returns
Recommendation: inspect your code that accesses method parameters. It can usually be simplified using the newly introduced methods.
Synthetic and mandated parameters
In addition to parameters explicitly declared in the source code, bytecode generators (such as
javac) sometimes have to emit additional parameters.
When these parameters are prescribed by the Java Language Specification (JLS), they are called implicitly declared or mandated.
Otherwise, they are an unspecified artifact of compiler implementation and are called synthetic.
Jandex used to pretty much not care. Sometimes, you would see these extra parameters, while sometimes, you would not.
In Jandex 3.0.0, the parameter accessing methods mentioned above never return synthetic and mandated parameters.
(At least in the vast majority of cases.
Jandex uses the
Signature bytecode attribute and a few simple heuristics to figure out the list of explicitly declared parameters.
javac emits the
Signature attribute for all methods that have synthetic/mandated parameters, even if it wouldn’t have to, so classes compiled with
javac should be fine.
There are rare cases, involving constructors of local/anonymous classes that capture lexically enclosing variables, where Jandex will return synthetic/mandated parameters for classes compiled with ECJ.)
For special occasions where access to the full list of method parameters (based on the method descriptor) is required, there are 2 more methods:
descriptorParametersCount(): returns the number of parameters including synthetic/mandated
descriptorParameterTypes(): returns the list of parameter types including synthetic/mandated
The parameter types obtained from method descriptor are never annotated and their position in the list cannot be used to obtain a parameter name.
Recommendation: in most cases, you don’t need these methods.
parameter* methods by default and only resort to
descriptorParameter* when you need to.
Proper representation of recursive type parameters
Jandex includes a faithful representation of the Java type system, including generic types. Type parameters, and type variables in general, used to be represented by one of the following 2 classes:
A type variable can be unresolved for example when you’re indexing an incomplete classpath. However, with Jandex 2.x, a type variable may also be unresolved when it occurs in its own definition.
For example, one often defines type parameters like
<T extends Comparable<T>>.
This type parameter definition is recursive in
Since Jandex types generally do not form cycles, the first occurence of
T is represented as
TypeVariable, but the second occurence as
To become more faithful yet still avoid cycles in the object model, Jandex 3.0.0 includes an additional representation of type variables occuring in their own definition:
With Jandex 3.0.0, the second occurence of
T in the example above is represented as
A reference may be
follow()-ed to obtain the type variable it points to.
Recommendation: if your code has special cases for handling
Type.Kind.UNRESOLVED_TYPE_VARIABLE, it is a good sign that it needs to be updated to deal with
And even if not, it is a good idea to test your code with some recursively defined type parameters.
Recommendation: if your code processes types in a recursive manner, you need to take care to avoid infinite regress.
TypeVariableReference and processing the resulting
TypeVariable recursively is most likely not what you want.
Indexer.index() return type
Indexer.index(InputStream) method, as well as the
indexClass(Class<?>) method, used to return
ClassInfo of the just-indexed class.
This is convenient, but prevents additional post-processing during
In Jandex 2.x, there was no such post-processing, but Jandex 3.0.0 adds some. Notably, post-processing is required for propagating type annotations on type variables across nested classes, as well as resolving unresolved type variables across nested classes and patching the resulting type variable references.
BREAKING CHANGE: the
indexClass(Class<?>) methods now have a return type of
You have to build a complete
Index to be able to obtain a
Recommendation: if you create a one-off
Indexer to index a single class, you can simplify your code using
Note that callers of
indexClass() compiled against Jandex 2.x will continue running, because Jandex 3.0.0 includes a synthetic bridge method for binary compatibility.
That bridge method always returns
If you want to keep compiling against Jandex 2.x and add compatibility with Jandex 3.0.0, you need to ignore the return value of
indexClass(), or at least handle
null result gracefully.
Jandex 2.x only indexes annotations with
With Jandex 3.0.0, annotations with retention of
CLASS are indexed as well.
BREAKING CHANGE: this is technically a breaking change, but shouldn’t really affect anyone.
Recommendation: you can distinguish class-retained annotations from runtime-retained by calling
Navigation for interfaces and packages
Some methods were added to
Index, and actually to
IndexView, to navigate the interface hierarchy and package structure:
getKnownDirectSubinterfaces(): returns all known direct subinterfaces of the specified interface
getAllKnownSubinterfaces(): returns all known interfaces that extend the given interface, directly and indirectly
getClassesInPackage(): returns all classes present in given package (but not in subpackages)
getSubpackages(): returns direct subpackages of given package (but not indirect subpackages)
BREAKING CHANGE: this is a breaking change if you implement the
Such implementations typically delegate to some other
IndexView, in which case, adaptation should be straightforward.
Otherwise, consult the javadoc of these methods for more precise description.
Jandex 3.0.0 doesn’t break the behavior of
These methods are still inconsistent in that
getKnownDirectImplementors() returns subinterfaces and classes implementing the interface, while
getAllKnownImplementors() only returns classes implementing the interface.
It was tempting to fix this inconsistency, but in the end, we decided it was not worth the potential trouble.
To make life of Quarkus extension authors that create their own
AnnotationInstances easier, we introduced a builder.
The previously existing
AnnotationInstance.create() methods are not going away, they are not even deprecated, they are just more difficult to use.
AnnotationInstance.builder() instead of
Maven plugin changes
As mentioned above, the Jandex Maven plugin has been merged into the Jandex codebase. Moving forward, you won’t have to track which Jandex Maven plugin version corresponds to which Jandex release: they are released together now and always have the same version number.
There are some changes and improvements in the Maven plugin, too.
First of all, one execution of the Jandex Maven plugin now always produces a single index. Previously, each file set configured in the Jandex Maven plugin execution produced its own index. This is counter-intuitive and usually not what you need.
BREAKING CHANGE: this is a breaking change, but shouldn’t hopefully affect anyone.
Recommendation: use multiple executions of the Jandex Maven plugin if you need to produce multiple Jandex indices during Maven build.
Further, the file set configuration allows configuring a dependency in addition to a directory. This is useful if your artifact should carry an index including not only its own classes, but also classes from some of its dependencies.
And lastly, a new goal
jandex-jar was added to the Maven plugin to allow reindexing an already existing JAR.
This is useful for example in combination with shading.
Recommendation: the Maven plugin is described pretty well in the Jandex documentation.
Other smaller changes
There’s a few more changes, but those are smaller and less impactful, so we’ll just describe them briefly here.
BREAKING CHANGE: the
IndexReader.getDataVersion() method was removed.
To the best of our knowledge, noone has actually ever used it, and the return value was wrong (didn’t conform to the contract stated in the javadoc).
getIndexVersion() method remains intact.
A notion of equivalence of Jandex objects was added.
This is useful when building more advanced layers on top of Jandex that deal with annotation overlays and similar things.
If you’re interested, see the
The methods on
IndexView that accept a class name as a
DotName now have more convenient overloads that accept a
String and even
If you search the index for classes that you have on your classpath, using these new methods can simplify your code.
For example, instead of
index.getClassByName(DotName.createSimple(MyClass.class.getName())), you can call
ClassInfo.isInterface() was added.
It can be used to determine whether given
ClassInfo actually represents an interface.
ClassInfo.memberClasses() was added, which returns a set of
DotNames of member classes of given class.
To inspect those member classes more deeply, you need to look them up in an
JandexReflection was added, containing some utility methods to load classes corresponding to Jandex
The classes are by default loaded from TCCL, but if there’s none, the class loader that loaded
JandexReflection itself is used.
Jandex 3.0.0 contains many interesting features and improvements. This unfortunately required a few breaking changes. Here’s the recommended migration path:
Upgrade to Jandex 2.4.3.Final. This release deprecates some of the methods that are changed in Jandex 3.0.0 and offers replacements. Notably, this includes
parameterTypes()instead). Make sure you don’t use any deprecated Jandex methods.
Make sure you don’t use
indexClass()'s return value.
At this point, if you compile your code against Jandex 2.4.3.Final, it will most likely run just fine against both 2.4.3.Final and 3.0.0. Exceptions include: if you implement
IndexView(there are new methods) or if you use the Jandex
Typehierarchy extensively (you’ll need to handle
Upgrade to Jandex 3.0.0. Configure Maven Enforcer plugin to ban
org.jboss:jandexfrom getting into your dependency tree. With the exception of situations listed in the previous item, your code should compile and run without an issue.
With this release, Jandex also has a new documentation site. It is currently rather incomplete, but that will hopefully improve over time. Jandex javadoc was also improved on many places, and remains the go-to reference.
If you experience any troubles, or if you have any exciting ideas for Jandex, please file an issue.