Gradle annotationProcessor works, but implementation don't for Annotation Processors

Hello, I have a question regarding gradle and annotation processors.

javac documentation says that if you don’t specify -processorpath then --class-path also will be used to search for annotation processors.

If I undestand it correctly, javac is searching for file META-INF/services/javax.annotation.processing.Processor where you specify processor class

--class-path path, -classpath path, or -cp path
Specifies where to find user class files and annotation processors. This class path overrides the user class path in the CLASSPATH environment variable.

If --class-path, -classpath, or -cp are not specified, then the user class path is the value of the CLASSPATH environment variable, if that is set, or else the current directory.

If not compiling code for modules, if the --source-path or -sourcepath` option is not specified, then the user class path is also searched for source files.

If the -processorpath option is not specified, then the class path is also searched for annotation processors.

I tried to create annotation processor (which just prints in console) by hand with javac and it was working good without specifying -processorpath just to check if javac documentation don’t lie.

javac com/annotationprocessor/BuilderProcessor.java com/annotationprocessor/BuilderProperty.java

javac --class-path "./resources:." com/annotationuser/Person.java com/annotationuser/PersonApp.java

After second command I got “Hello World” in console.

But when I use gradle as a build tool, my annotation processor works only when I use annotationProcessor dependency configuration. If I use compileOnly or implementation configuration, annotation processor just don’t work.
But I don’t understand why, because javac documentation says that if there are no -processpath
then -classpath will be used for searching for annotation processors

Here is example. This is not my project. I got this link by watching this video
link

This project uses mapstruct and mapstruct-processor dependencies.
And for mapstruct-processor dependency this project uses annotationProcessor configuration.

But if you search in mvnrepository there is this:

// https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor
implementation group: 'org.mapstruct', name: 'mapstruct-processor', version: '1.5.2.Final'

https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor/1.5.2.Final

Or if you search in maven central repository

implementation 'org.mapstruct:mapstruct-processor:1.5.2.Final'

But I don’t understand. As I understand this correctly, annotation processing will not be working if you use implementation. So I don’t understand why in two maven repositories there is this copy-paste coordinates with ‘implementation’, but not ‘annotationProcessor’

Also I don’t understand why ‘implementation’/‘complileOnly’ is not working. Javac documentation says that if you don’t specify -processorpath then annotation processors will be used from --class-path.
As I understand ‘annotationProcessor’ configuration in gradle adds dependency to -processorpath.
But I don’t understand, if I use ‘implementation’ which adds dependency to compilation class-path why this is not works?

Thank you for your help!

Well, your confusion comes from the fact that you are reading documentation of one tool (javac) and assume from that rules for another tool (Gradle).
Gradle intentionally makes it an explicit choice which annotation processors should be used.
It was different in the past, but Gradle changed this to always be an explicit choice as the implicit behavior is actually more confusing and disturbing and probably also makes problem with up-to-date checks and similar I guess. Besides that annotation processors on implementationwould also pollute the runtime classpath unnecessarily, …

Tools like the MavenCentral search or mvnrepository.com are not aware of whether a dependency is a runtime dependeny or an annotation processor and sometimes jars are even both (even though it would be better to have separate artifacts as it is just pollution to have the annotation processor at runtime). What they show is example code of how to use the given artifact as a code dependency. They also show not that you could use it as api or compileOnly or compileOnlyApi or …
There are even pages or docs that still show to use compile even though it was deprecated already many years and in the meantime even got removed.

1 Like

Ah, it’s even explicitly in the Gradle documentation including reasoning:

Compilation avoidance

If a dependent project has changed in an ABI-compatible way (only its private API has changed), then Java compilation tasks will be up-to-date. This means that if project A depends on project B and a class in B is changed in an ABI-compatible way (typically, changing only the body of a method), then Gradle won’t recompile A.
[…]
Since implementation details matter for annotation processors, they must be declared separately on the annotation processor path. Gradle ignores annotation processors on the compile classpath.

So it is actually a necessity and not just arbitrary decision.
With javac such necessity does not exist.

1 Like