quarkus: JAXB unmarshalling fails in native mode

Describe the bug

When a class annotated with JAXB annotations is unmarshalled, the unmarshalling fails with

...
Caused by: java.lang.RuntimeException: org.glassfish.jaxb.runtime.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Property expression appears in @XmlType.propOrder, but no such property exists. Maybe you meant executorService?
	this problem is related to the following location:
		at org.apache.camel.model.ThrottleDefinition

	at io.quarkus.jaxb.runtime.JaxbContextProducer.createJAXBContext(JaxbContextProducer.java:82)
	at io.quarkus.jaxb.runtime.JaxbContextProducer.jaxbContext(JaxbContextProducer.java:31)
	at io.quarkus.jaxb.runtime.JaxbContextProducer_ProducerMethod_jaxbContext_6a6d20272304edd64c7bdfa35e3ed5d5971a3949_Bean.doCreate(Unknown Source)
	at io.quarkus.jaxb.runtime.JaxbContextProducer_ProducerMethod_jaxbContext_6a6d20272304edd64c7bdfa35e3ed5d5971a3949_Bean.create(Unknown Source)
	at io.quarkus.jaxb.runtime.JaxbContextProducer_ProducerMethod_jaxbContext_6a6d20272304edd64c7bdfa35e3ed5d5971a3949_Bean.create(Unknown Source)
	at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:113)
	at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:37)
	at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:34)
	at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26)
	at io.quarkus.arc.impl.ComputingCache.computeIfAbsent(ComputingCache.java:69)
	at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:34)
	at io.quarkus.jaxb.runtime.JaxbContextProducer_ProducerMethod_jaxbContext_6a6d20272304edd64c7bdfa35e3ed5d5971a3949_Bean.get(Unknown Source)
	at io.quarkus.jaxb.runtime.JaxbContextProducer_ProducerMethod_jaxbContext_6a6d20272304edd64c7bdfa35e3ed5d5971a3949_Bean.get(Unknown Source)
	at io.quarkus.arc.impl.Instances$3.get(Instances.java:132)
	at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26)
	at io.quarkus.arc.impl.LazyInstanceHandle.instanceInternal(LazyInstanceHandle.java:32)
	at io.quarkus.arc.impl.AbstractInstanceHandle.get(AbstractInstanceHandle.java:46)
	at de.turing85.converter.FooConverter.getContext(FooConverter.java:51)
	at de.turing85.converter.FooConverter.unmarshalFromString(FooConverter.java:39)
	at de.turing85.converter.FooConverter.unmarshal(FooConverter.java:34)
	at java.base@21/java.lang.reflect.Method.invoke(Method.java:580)
	at org.apache.camel.support.ObjectHelper.invokeMethod(ObjectHelper.java:355)
	... 28 more
...

Expected behavior

The object gets unmarshalled

Actual behavior

The above exception is thrown.

How to Reproduce?

Reproducer:

  • Clone https://github.com/turing85/quarkus-camel-jaxb
    git clone https://github.com/turing85/quarkus-camel-jaxb.git
    cd quarkus-camel-jaxb
    
  • Build the application, run the tests
    ./mvnw clean verify
    
  • Observe that the tests succeed
  • Build the application natively, run the tests
    ./mvnw --define native clean verify
    
  • Observe that the tests fail with above exception

Output of uname -a or ver

Linux ecco 5.15.0-52-generic #58-Ubuntu SMP Thu Oct 13 08:03:55 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Output of java -version

openjdk version “17.0.8” 2023-07-18 OpenJDK Runtime Environment Temurin-17.0.8+7 (build 17.0.8+7) OpenJDK 64-Bit Server VM Temurin-17.0.8+7 (build 17.0.8+7, mixed mode, sharing)

GraalVM version (if different from Java)

mandrel-23.0.1.2-java17

Quarkus version or git rev

3.2.6.Final

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

Apache Maven 3.9.3 (21122926829f1ead511c958d89bd2f672198ae9f) Maven home: /home/marco/.m2/wrapper/dists/apache-maven-3.9.3-bin/326f10f4/apache-maven-3.9.3 Java version: 17.0.8, vendor: Eclipse Adoptium, runtime: /opt/java/mandrel/23.0.1.2-java17 Default locale: en_US, platform encoding: UTF-8 OS name: “linux”, version: “5.15.0-52-generic”, arch: “amd64”, family: “unix”

Additional information

  • Notice that neither Foo.java nor Bar.java define propOrder. Defining them does not change the behaviour.
  • Notice that registering the classes Foo and Bar for reflectoin and serialization does not change the behaviour.

About this issue

  • Original URL
  • State: open
  • Created 9 months ago
  • Comments: 30 (14 by maintainers)

Most upvoted comments

@rysurd I’m not sure it’s worth being more fine-grained for the specific fields. Just adding the parent would work. Using ReflectiveHierarchyBuildItem at the line @zakkak pointed out should be enough to fix the issue.

I would really appreciate if we could add a test to the -deployment module in passing so that it doesn’t get broken again later.

However I’m wondering if a better fix would be to go deeper and see why ExpressionNode wasn’t automatically marked for reflexion … Which is weird.

@rysurd If I am getting this right, there is currently no logic in Quarkus for registering it automatically. In https://github.com/quarkusio/quarkus/blob/4a5006e3240c65688c19e48bb22a04c65272bdfa/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java#L207 Quarkus registers the annotated (with XMlType) classes themselves for reflection, but doesn’t do this reflectively for their superclasses.

I believe the right fix is to change the logic there to register the classes reflectively (using io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem.Builder).

Regarding @gsmet’s comment of only registering the fields mentioned in propOrder, although it sounds the right thing to do I am afraid we currently lack the support for doing such fine grain registrations in Quarkus, so you would need to extend io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem and io.quarkus.deployment.steps.NativeImageReflectConfigStep as well. Probably best done in a separate PR.

@gsmet I found something really interesting. I added -H:ReflectionConfigurationFiles=reflection-config.json flag to the reproducer’s application.properties in order to add this config :

[
  {
    "name" : "org.apache.camel.model.ExpressionNode",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true
  }
]

And this time it worked. So explicitly marking ExpressionNode for reflection seems to solve our issue, meaning the problem is the fact that this class is not marked for reflection at all. Is there a way within quarkus to do this? Mark a class for reflection without the need to add a json config file?

Thanks a lot!

EDIT : Found it. I added

addReflectiveClass(reflectiveClass, true, true, "org.apache.camel.model.ExpressionNode");

At line 275 in JaxbProcessor and it worked! I’ll do more tests and if good I’m adding an integration test and will open a PR. However I’m wondering if a better fix would be to go deeper and see why ExpressionNode wasn’t automatically marked for reflexion … Which is weird.

The tests needs to be added to https://github.com/quarkusio/quarkus/tree/main/integration-tests/jaxb .

I would reproduce the fact that expression is in a parent as it’s interesting, especially if someone wants to experiment with being more fine grained for propOrder.

So I dug a bit deeper and found the message template for the error message. The template is:

PROPERTY_ORDER_CONTAINS_UNUSED_ENTRY = \
    Property {0} appears in @XmlType.propOrder, but no such property exists. Maybe you meant {1}?

Thus, it so not Property expression, but Property "expression". Now the question is where this comes from. And where executorService comes from. This pretty much looks like JAXB is very, very confused.

@rysurd The exception you posted indicates that the said class cannot be found on the classpath.

Please check to see if org.glassfish.hk2:osgi-resource-locator (the maven artefact providing org.glassfish.hk2.osgiresourcelocator.ServiceLoader) is in the project’s dependencies. You can do this with something like:

mvn dependency:tree | grep osgi-resource-locator

If the dependency is present check to see if it’s declared as optional, when compiling native images only compile dependencies are considered, so you might need to explicitly add the dependency as a compile dependency.

Sure will do, thanks! I was a bit struggling with setting up the local env to start the reproducer but now it’s all done and good.

@ibourdier I’m not aware of some working on this actively. please go ahead and dive into the reproducer and investigate.

@ibourdier feel free. The ticket is unassigned. Although it might be worth consulting @gsmet first.