Backend Development 8 min read

Injecting Version Information into Java JARs Using a Compile‑Time Annotation Processor

This article demonstrates how to create a custom compile‑time annotation processor that automatically injects the JAR version into Java constants, enabling Prometheus monitoring of component versions without manual updates, and walks through the full implementation, registration, and testing steps.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Injecting Version Information into Java JARs Using a Compile‑Time Annotation Processor

Insertion‑style annotation processors, similar to Lombok, allow compile‑time modification of the abstract syntax tree (AST); the author introduces this concept and explains the motivation to automatically embed JAR version numbers into internal Java component libraries for Prometheus monitoring.

The company provides a set of reusable Java modules (circuit‑breaker, load‑balancer, RPC, etc.) packaged as JARs; they need to expose each JAR's version to Prometheus so they can track usage ratios of different versions across the organization.

Manually maintaining a version constant in each module is error‑prone; the proposed solution is to define a custom annotation (e.g., @TrisceliVersion ) and let a compile‑time processor replace the constant with the actual version during the Gradle build, just like Lombok generates getters/setters.

The custom annotation is declared as:

@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD})
public @interface TrisceliVersion {}

The processor extends AbstractProcessor and implements the required methods:

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
getSupportedAnnotationTypes() {
        HashSet
set = new HashSet<>();
        set.add(TrisceliVersion.class.getName());
        return set;
    }

    @Override
    public boolean process(Set
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());
            }
        }
        return true;
    }

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

    private String getVersion() { return "v1.0.1"; }
}

To make the processor discoverable, a service provider configuration file META-INF/services/javax.annotation.processing.Processor (or the newer javax.annotation.processing.Processor path) must list the fully‑qualified name of TrisceliVersionProcessor .

Testing involves creating a test module that depends on the processor‑containing library, adding the @TrisceliVersion annotation to a static String version field, and running gradle build . The resulting bytecode shows the field initialized with the injected version value.

The author concludes that insertion‑style annotation processors are a powerful way to manipulate generated bytecode, enabling advanced plugins beyond Lombok and opening possibilities for creative compile‑time code generation.

BackendJavaGradlePrometheusannotation-processingcompile-timeversion-injection
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login 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.