quarkus: Indexed `provided` annotation breaks `build` goal (or app startup) in 2.14

Describe the bug

While trying to upgrade from 2.13.3 to 2.14.0/1 I was greeted with a strange java.lang.ClassNotFoundException: lombok.NonNull very early in the boot process of the app.

This is in a multi-module project that is working just fine with Quarkus 2.13.3 (and the “old” Jandex 1.2.3).

In a non-app module, a class is using lombok.NonNull on a field alongside lombok.RequiredArgsConstructor. jandex-maven-plugin is executed in the module to provide an index. The lombok jar is a provided dependency (as recommended generally). See the reproducer further down.

Expected behavior

Should work as in 2.13

Actual behavior

Fails at app startup (with ECJ) or already when building the app via build goal (with standard compiler).

How to Reproduce?

q_jandex_nonnull.zip

mvn clean verify:

[INFO] --- quarkus-maven-plugin:2.14.1.Final:build (default) @ code-with-quarkus-app ---
[WARNING] [io.quarkus.arc.processor.BeanArchives] Failed to index lombok.NonNull: Class does not exist in ClassLoader QuarkusClassLoader:Deployment Class Loader: PROD@65f470f8
[INFO] [io.quarkus.arc.processor.IndexClassLookupUtils] Class for name: lombok.NonNull was not found in Jandex index. Please ensure the class is part of the index.
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for code-with-quarkus 1.0.0-SNAPSHOT:
[INFO] 
[INFO] code-with-quarkus .................................. SUCCESS [  0.027 s]
[INFO] code-with-quarkus-lib .............................. SUCCESS [  0.924 s]
[INFO] code-with-quarkus-app .............................. FAILURE [  1.511 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.968 s
[INFO] Finished at: 2022-11-22T23:13:14+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:2.14.1.Final:build (default) on project code-with-quarkus-app: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR] 	[error]: Build step io.quarkus.arc.deployment.ArcProcessor#generateResources threw an exception: java.lang.IllegalStateException: java.util.concurrent.ExecutionException: java.lang.NullPointerException: Annotation class not available: @NonNull
[ERROR] 	at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:918)
[ERROR] 	at io.quarkus.builder.BuildContext.run(BuildContext.java:281)
[ERROR] 	at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
[ERROR] 	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
[ERROR] 	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
[ERROR] 	at java.base/java.lang.Thread.run(Thread.java:833)
[ERROR] 	at org.jboss.threads.JBossThread.run(JBossThread.java:501)
[ERROR] Caused by: java.util.concurrent.ExecutionException: java.lang.NullPointerException: Annotation class not available: @NonNull
[ERROR] 	at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
[ERROR] 	at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
[ERROR] 	at io.quarkus.arc.processor.BeanProcessor.generateResources(BeanProcessor.java:319)
[ERROR] 	at io.quarkus.arc.deployment.ArcProcessor.generateResources(ArcProcessor.java:570)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR] 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
[ERROR] 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR] 	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
[ERROR] 	at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:909)
[ERROR] 	... 6 more
[ERROR] Caused by: java.lang.NullPointerException: Annotation class not available: @NonNull
[ERROR] 	at java.base/java.util.Objects.requireNonNull(Objects.java:233)
[ERROR] 	at io.quarkus.arc.processor.AnnotationLiteralProcessor.create(AnnotationLiteralProcessor.java:87)
[ERROR] 	at io.quarkus.arc.processor.BeanGenerator.collectInjectionPointAnnotations(BeanGenerator.java:1937)
[ERROR] 	at io.quarkus.arc.processor.BeanGenerator.wrapCurrentInjectionPoint(BeanGenerator.java:1837)
[ERROR] 	at io.quarkus.arc.processor.BeanGenerator.initConstructor(BeanGenerator.java:704)
[ERROR] 	at io.quarkus.arc.processor.BeanGenerator.createConstructor(BeanGenerator.java:614)
[ERROR] 	at io.quarkus.arc.processor.BeanGenerator.generateClassBean(BeanGenerator.java:355)
[ERROR] 	at io.quarkus.arc.processor.BeanGenerator.generate(BeanGenerator.java:122)
[ERROR] 	at io.quarkus.arc.processor.BeanProcessor$4.call(BeanProcessor.java:258)
[ERROR] 	at io.quarkus.arc.processor.BeanProcessor$4.call(BeanProcessor.java:254)
[ERROR] 	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[ERROR] 	... 5 more

mvn clean verify -Pquarkus-2.13.4 is fine though (switches from Quarkus 2.14.1 to 2.13.4 and jandex-plugin to the old coordinates and version)

Please note that this reproducer is not using ECJ (unlike my actual project), hence the error at build time, not boot time. If required I can provide a ECJ setup like in https://github.com/smallrye/jandex/issues/268. I actually started with an ECJ setup, stripped it down and was then surprised by the different behavior.

Output of uname -a or ver

No response

Output of java -version

17.0.5

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.14.1

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

I think it’s an issue in Jandex (cc @Ladicek), but I’m not entirely sure. Might also be related to Arc (cc @mkouba @manovotn )?

If I had to guess (wildly), I’d suppose that Jandex 3 is more strict about missing annotations.

Please note that there have been discussions about why lombok.NonNull has CLASS retention in the first place: https://github.com/projectlombok/lombok/issues/895 I do think the maintainer has a point though: https://github.com/projectlombok/lombok/issues/895#issuecomment-145653045

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 17 (17 by maintainers)

Most upvoted comments

Fix confirmed in 2.14.2! Thanks all, especially @Ladicek .

Yes, I’m not talking about “processing” errors but “semantic” errors (i.e. a class is annotated with a qualifier but the qualifier class is not available).

Okay, so what I’m doing is:

  1. AnnotationLiteralProcessor will still throw if the annotation class is null, and most callers don’t check that. That’s because most callers deal with qualifiers and interceptor bindings, and those must always exist. So if a qualifier class or an interceptor binding class is missing, we’ll still fail. There are 2 places that deal with arbitrary annotations on injection points, and those will just check if the annotation class exists or not.
  2. This is an extra and we wouldn’t really have to do it, but I think we want to: AnnotationLiteralProcessor will additionally throw if the annotation is class-retained. Again, most callers deal with qualifiers and interceptor bindings, and those must always be runtime-retained. So if a qualifier or interceptor binding is class-retained, we’ll fail. I think that’s good. The same 2 places mentioned in previous item are modified to just skip class-retained annotations.

WDYT?

EDIT: if you want to see the code, it’s here: https://github.com/Ladicek/quarkus/commits/filter-annotation-literals I want to run CI first in my fork before opening a PR.

Might be an unpopular opinion but given it’s a very new feature, if we think it was a bad decision and we want to fix it, it’s probably still possible if we document it properly in the announcement. I don’t think it’s widely used in the wild. Better than live with it forever.

That sounds sensible. I’ll see if I can come up with a better API.

So I found that most of the time, AnnotationLiteralProcessor is used with CDI annotations (qualifiers and interceptor bindings) and there are just a few places where it can be used with other annotations (when it’s fed with all annotations from an injection point). So from ArC perspective, it’s probably best if AnnotationLiteralProcessor actually not only checks that the annotation class is available, but also that it’s runtime-retained – if it’s by accident called with an annotation that doesn’t satisfy those conditions, we wanna know. Those few places that deal with arbitrary injection point annotations can be updated to filter the annotations themselves.

@mkouba Do you have anything specific in mind? Plenty people have asked for Jandex to include class-retained annotations, so we can’t really move back. ArC unfortunately calls the Jandex API directly on many places, so modifying the AnnotationStore to filter out class-retained annotations won’t help. I really should start working on the annotation overlay in Jandex, which should allow centralizing many things that are currently done in an ad-hoc fashion…