How to Auto‑Inject JAR Version Numbers with a Custom Java Annotation Processor

This article explains how to create a compile‑time annotation processor that automatically injects the JAR version into a static constant, enabling Prometheus to monitor version usage without manual updates, and demonstrates the implementation with Lombok‑style code examples.

Java Backend Technology
Java Backend Technology
Java Backend Technology
How to Auto‑Inject JAR Version Numbers with a Custom Java Annotation Processor

Requirement

Our company provides a generic Java component library (circuit‑breaker, load‑balancer, RPC, etc.) packaged as JARs and published to an internal repository. We want to monitor the usage ratio of each version with Prometheus, as shown in the diagram.

Prometheus usage diagram
Prometheus usage diagram

Problem

Manually writing the JAR version into a constant or configuration file for every component is error‑prone and must be updated on each release.

We would like Gradle to obtain the JAR version during the build and inject it automatically, similar to how Lombok generates getters and setters via an annotation.

Solution

Define a custom annotation @TrisceliVersion and an insert‑style annotation processor that runs at compile time (JSR‑269 / Pluggable Annotation Processing API). The processor reads the version information and replaces the constant’s initializer in the abstract syntax tree.

Annotation definition

@Documented
@Retention(RetentionPolicy.SOURCE) // only present at compile time
@Target({ElementType.FIELD})
public @interface TrisceliVersion {}

Processor implementation

public class TrisceliVersionProcessor extends AbstractProcessor {
    private JavacTrees javacTrees;
    private TreeMaker treeMaker;
    private ProcessingEnvironment processingEnv;

    @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());
        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 support.");
                }
                jcv.init = treeMaker.Literal(getVersion());
            }
        }
        return true;
    }

    private void printErrorMessage(Element e, String m) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, m, e);
    }

    private String getVersion() {
        // In a real implementation this would read the actual JAR version.
        return "v1.0.1";
    }
}

The processor is discovered via the SPI mechanism by adding its fully‑qualified name to META-INF/services/javax.annotation.processing.Processor.

SPI registration diagram
SPI registration diagram

Testing

Create a test module that depends on the component library, annotate a static final String field with @TrisceliVersion, build with Gradle, and the generated bytecode contains the injected version value.

Test module screenshot
Test module screenshot

This demonstrates how insert‑style annotation processors can modify the AST to produce customized bytecode, opening the door to many powerful compile‑time plugins.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaGradlePrometheusAnnotation ProcessorCompile-timeVersion Injection
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.