Summary
When a <xs:choice> group's member elements are typed with complex types that share a common
ancestor via xs:extension, and those types are defined in a different Maven module that the
current module depends on via a plain <dependency> (not listed under the jaxb-maven-plugin's
own <configuration><plugins>), XJC/-Xinheritance correctly infers the common ancestor as the
collection element type up through 4.0.12. Starting with 4.0.13 it silently falls back to
List<Object>.
Bisected to PR #668 ("move to maven-resolver-api", fixing #663), released in 4.0.13.
Root cause (as far as I can tell)
- Pre-4.0.13,
resolveXJCPluginArtifacts() built an ArtifactResolutionRequest with
setArtifact(project.getArtifact()) and setResolveRoot(false). That combination, via the
legacy Maven RepositorySystem, resolved not only the declared <plugins> dependencies but
also (as an apparent side effect) the current module's own <dependencies> graph, putting
their compiled classes on the XJC plugin classloader.
- Post-4.0.13 (
ArtifactResolverUtils.resolveTransitively), the Aether CollectRequest uses a
bare-coordinate root artifact (no dependency info) and only collectRequest.setDependencies(...)
from the <plugins> list - the module's own <dependencies> are never folded in.
- If a module's XSD choice group references types whose common ancestor class is only reachable
through a project <dependency> (typical when that ancestor is defined and JAXB-compiled in a
separate schema module, exposed to this module only via an <episode> reference), that
ancestor class is no longer visible on the XJC classpath, and the common-base-type computation
(whichever plugin/code performs it under -Xinheritance) can't resolve it, silently degrading
to Object.
I want to stress "silently" - this doesn't fail the build, it just changes generated code, which
then breaks callers that assigned the getter's return value to a List<TheAncestorType> variable
(as would compile against 4.0.12-generated sources).
Minimal reproduction
Attached: jaxb-repro.zip — two throwaway Maven modules.
module-a: XSD defines Shape (base complexType) and Circle/Square (extend Shape).
Generated + registers an episode.
module-b: depends on module-a as a plain <dependency> (not under <plugins>), imports
module-a's namespace via the episode, and defines:
<xs:element name="ShapeListResponse">
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Circle" type="a:Circle"/>
<xs:element name="Square" type="a:Square"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
</xs:element>
Toggle the jaxb-plugins.version property in the parent pom.xml and rebuild:
mvn clean install -Djaxb-plugins.version=4.0.12 # -> public List<Shape> getCircleOrSquare()
mvn clean install -Djaxb-plugins.version=4.0.13 # -> public List<Object> getCircleOrSquare()
Verified on both mvn and mvnd, JDK 21.
Workaround
Explicitly redeclare the ancestor-providing module under the jaxb-maven-plugin's own
<configuration><plugins> list (with combine.children="append" if you don't want to repeat the
whole default list), so its classes stay on the XJC classloader regardless of resolver internals:
<plugins combine.children="append">
<plugin>
<groupId>org.example.jaxbrepro</groupId>
<artifactId>module-a</artifactId>
<version>${project.version}</version>
</plugin>
</plugins>
This works but has to be applied per affected module, and is easy to miss since the failure mode
is silent (no build error, just a behavior change in generated code).
Ask
Would it make sense for resolveXJCPluginArtifacts() to also fold in the current project's own
compile-scope dependencies (optionally behind a flag, since I understand the 4.0.13 change may
have been intentional scope-narrowing rather than purely a side-effect of the API migration)?
Happy to open a PR if a maintainer can confirm the desired behavior.
Summary
When a
<xs:choice>group's member elements are typed with complex types that share a commonancestor via
xs:extension, and those types are defined in a different Maven module that thecurrent module depends on via a plain
<dependency>(not listed under the jaxb-maven-plugin'sown
<configuration><plugins>), XJC/-Xinheritancecorrectly infers the common ancestor as thecollection element type up through 4.0.12. Starting with 4.0.13 it silently falls back to
List<Object>.Bisected to PR #668 ("move to maven-resolver-api", fixing #663), released in 4.0.13.
Root cause (as far as I can tell)
resolveXJCPluginArtifacts()built anArtifactResolutionRequestwithsetArtifact(project.getArtifact())andsetResolveRoot(false). That combination, via thelegacy Maven
RepositorySystem, resolved not only the declared<plugins>dependencies butalso (as an apparent side effect) the current module's own
<dependencies>graph, puttingtheir compiled classes on the XJC plugin classloader.
ArtifactResolverUtils.resolveTransitively), the AetherCollectRequestuses abare-coordinate root artifact (no dependency info) and only
collectRequest.setDependencies(...)from the
<plugins>list - the module's own<dependencies>are never folded in.through a project
<dependency>(typical when that ancestor is defined and JAXB-compiled in aseparate schema module, exposed to this module only via an
<episode>reference), thatancestor class is no longer visible on the XJC classpath, and the common-base-type computation
(whichever plugin/code performs it under
-Xinheritance) can't resolve it, silently degradingto
Object.I want to stress "silently" - this doesn't fail the build, it just changes generated code, which
then breaks callers that assigned the getter's return value to a
List<TheAncestorType>variable(as would compile against 4.0.12-generated sources).
Minimal reproduction
Attached:
jaxb-repro.zip— two throwaway Maven modules.module-a: XSD definesShape(base complexType) andCircle/Square(extendShape).Generated + registers an episode.
module-b: depends onmodule-aas a plain<dependency>(not under<plugins>), importsmodule-a's namespace via the episode, and defines:Toggle the
jaxb-plugins.versionproperty in the parentpom.xmland rebuild:Verified on both
mvnandmvnd, JDK 21.Workaround
Explicitly redeclare the ancestor-providing module under the jaxb-maven-plugin's own
<configuration><plugins>list (withcombine.children="append"if you don't want to repeat thewhole default list), so its classes stay on the XJC classloader regardless of resolver internals:
This works but has to be applied per affected module, and is easy to miss since the failure mode
is silent (no build error, just a behavior change in generated code).
Ask
Would it make sense for
resolveXJCPluginArtifacts()to also fold in the current project's owncompile-scope dependencies (optionally behind a flag, since I understand the 4.0.13 change may
have been intentional scope-narrowing rather than purely a side-effect of the API migration)?
Happy to open a PR if a maintainer can confirm the desired behavior.