How To Use NetBeans For A Custom Annotation Processor

Java annotation processing can remove repetitive code from a project while keeping the source easy to read. A custom processor examines annotations during compilation and can create metadata, adapters, registries, validation reports, or source classes automatically.

NetBeans makes the workflow visible: create separate modules, inspect compiler output, run Maven goals, and navigate generated files from the IDE. The approach suits Australian teams working across Sydney, Melbourne, Brisbane, and other locations where consistent builds matter across different time zones and development environments.

Define The Annotation Contract

Begin with a small annotation in a dedicated Maven module. Keeping the annotation separate prevents the processor from depending on application classes and makes it possible for other projects to use the annotation without importing the complete processing implementation.

package com.example.codegen;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface GenerateBuilder {
}

SOURCE retention is suitable when the processor only needs the annotation during compilation. In NetBeans, create a Maven Java project for this API, select the required JDK, and use a package name that will remain stable once other applications depend on it.

Build The Processor Module

Create a second Maven module for the implementation. Add com.google.auto.service:auto-service-annotations if you want a convenient registration mechanism, along with the matching processor dependency. The processor extends AbstractProcessor and declares which annotation and Java language level it supports.

@SupportedAnnotationTypes("com.example.codegen.GenerateBuilder")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class BuilderProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations,
                           RoundEnvironment roundEnv) {
        for (Element element :
                roundEnv.getElementsAnnotatedWith(GenerateBuilder.class)) {
            TypeElement type = (TypeElement) element;
            // Inspect the type and write a source file here.
        }
        return true;
    }
}

For a small processor, Filer and the standard javax.lang.model API are enough. Use Elements to read names and documentation, Types to compare Java types, and Messager to report useful compiler diagnostics. The processor should generate only new files; modifying an existing source file causes compilation errors or unreliable incremental behaviour.

Register And Generate Source

The compiler must discover the processor through the service provider configuration. Without AutoService, add this file to the processor project:

src/main/resources/META-INF/services/
javax.annotation.processing.Processor

Its content should be the fully qualified processor class name. AutoService generates the file during the build, reducing the chance of a spelling or path error.

A generated class can be written with processingEnv.getFiler().createSourceFile(...). Build the output with a StringBuilder, a code-generation library, or a templating tool. Always derive the package and class name from the TypeElement, and guard against duplicate creation when multiple processing rounds occur. A concise error message is much easier to diagnose in NetBeans than a stack trace from a failed build.

Configure Maven In NetBeans

The application module needs the annotation API as a normal dependency and the processor on the annotation processor path. Recent Maven compiler plugin versions support an explicit configuration:

<annotationProcessorPaths>
  <path>
    <groupId>com.example</groupId>
    <artifactId>builder-processor</artifactId>
    <version>1.0.0</version>
  </path>
</annotationProcessorPaths>

Keep processor libraries away from the application runtime classpath where possible. This makes the dependency boundary clear and prevents implementation-only libraries from being packaged into a deployed service. Developers building a Spring application can pair this workflow with a Spring Boot project while keeping generated code in a dedicated build concern.

After changing the POM, use NetBeans to reload the Maven project, then run Clean and Build. Generated sources commonly appear under target/generated-sources/annotations. NetBeans may index them after the build; if code completion does not update immediately, reload the project or run the Maven goal again.

Test Diagnostics And Generated Files

A processor should be tested with both valid and invalid source examples. Verify that an annotated class produces the expected file, that an unannotated class produces nothing, and that malformed input creates a compiler error at the correct element. The Google compile-testing library can make these checks repeatable in unit tests.

Useful processor checks include:

  • Rejecting annotations placed on the wrong element type
  • Detecting duplicate generated class names
  • Reporting missing required annotation values
  • Confirming generated files compile in a second round
  • Checking behaviour with records, interfaces, and nested classes

Use Diagnostic.Kind.ERROR for conditions that must stop compilation and WARNING for recoverable issues. Avoid logging private source values unnecessarily. This is especially relevant for Australian health, finance, and government projects that may fall under the Privacy Act 1988 and its Australian Privacy Principles.

Make The Workflow Reliable

Generated code belongs to the build output, not the source directory, unless there is a strong reason to commit it. Add generated folders to the appropriate clean-up process, inspect them when debugging, and avoid manually editing files that will be recreated on the next build.

A reliable processor also benefits from predictable formatting and review practices:

  • Pin the JDK and Maven compiler versions
  • Use deterministic ordering for generated members
  • Keep generated headers clear and informative
  • Add CI checks for clean, repeatable generation
  • Document the annotation’s supported Java constructs

Australian developers often work from home or across AEST and AEDT schedules, so deterministic builds reduce “works on my machine” differences. Teams selling software into the local market should also consider Australian Consumer Law obligations for product representations and support documentation. Before committing style-sensitive generated files, configure NetBeans with an Eclipse formatter so local edits and generated output follow the same conventions.

A custom annotation processor is most valuable when its contract stays small, diagnostics remain clear, and generated code is treated as a reproducible build artefact. With separate Maven modules and explicit NetBeans configuration, the technique scales from a simple builder generator to validation, mapping, and integration code used across a larger Java platform.