Inject Jar Version into Java Components with Insertable Annotation Processors
This article demonstrates how to create a custom insertable annotation processor in Java to automatically inject the jar version into component constants at compile time, eliminating manual updates and enabling Prometheus monitoring of library usage across versions.
Requirement
Our company provides a set of common Java utility modules (circuit‑breaker, load‑balancer, RPC, etc.) packaged as JARs and published to an internal repository. We need to monitor the usage ratio of each version with Prometheus, as shown in the desired chart, to trace legacy users and manage compatibility.
Problem
Manually writing the version number into each component’s configuration or constant is error‑prone and requires updates on every release. We want a better solution, such as automatically injecting the version during the Gradle build, similar to how Lombok generates getters and setters via annotations.
Solution Overview
Java supports two ways to process annotations: compile‑time scanning and runtime reflection. Lombok’s
@Setterworks at compile time using the Pluggable Annotation Processing API (JSR‑269). We can define a custom insertable annotation processor that reads the JAR version at compile time and injects it into a constant field.
Defining the Annotation
<code>@Documented
@Retention(RetentionPolicy.SOURCE) // only present at compile time
@Target({ElementType.FIELD}) // can annotate fields only
public @interface TrisceliVersion {}
</code>Implementing the Processor
<code>public class TrisceliVersionProcessor extends AbstractProcessor {
private JavacTrees javacTrees;
private TreeMaker treeMaker;
private ProcessingEnvironment processingEnv;
@SneakyThrows
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.processingEnv = processingEnv;
this.javacTrees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> set = new HashSet<>();
set.add(TrisceliVersion.class.getName()); // supported annotation
return set;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement t : annotations) {
for (Element e : roundEnv.getElementsAnnotatedWith(t)) {
JCTree.JCVariableDecl jcv = (JCTree.JCVariableDecl) javacTrees.getTree(e);
String varType = jcv.vartype.type.toString();
if (!"java.lang.String".equals(varType)) {
printErrorMessage(e, "Type '" + varType + "' is not supported.");
}
jcv.init = treeMaker.Literal(getVersion()); // inject version value
}
}
return true;
}
private void printErrorMessage(Element e, String m) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, m, e);
}
private String getVersion() {
// In a real implementation, read the version from a file or build metadata.
return "v1.0.1";
}
}
</code>Registering the Processor
The processor must be discoverable via the SPI mechanism. Create a file
META-INF/services/javax.annotation.processing.Processorcontaining the fully qualified name of
TrisceliVersionProcessor.
Testing
Create a test module that depends on the utility library, add the
@TrisceliVersionannotation to a static
String versionfield, and run
gradle build. The generated bytecode will contain the injected version value.
Conclusion
Insertable annotation processors allow compile‑time modification of the abstract syntax tree, enabling powerful code generation such as automatic version injection. This technique can be extended to build sophisticated plugins like Lombok, reducing boilerplate and enhancing developer productivity.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.