This Gradle plugin allows integrating XJC code generation into a Gradle build.
Prerequisites
You need at least the following:
-
Gradle 5.6 or higher
-
JDK 8 or higher (for running Gradle)
Quick Start
Apply the org.unbroken-dome.xjc
plugin to your Gradle project:
plugins {
id 'org.unbroken-dome.xjc' version '2.0.0'
}
plugins {
id("org.unbroken-dome.xjc") version "2.0.0"
}
Put your XJC source files into src/main/schema
. This includes XML schema files (extension .xsd
),
binding customizations (extension .xjb
) and catalog files (extension .cat
).
Add a dependency on jaxb-api
to the implementation
configuration of your source set.
dependencies {
implementation 'javax.xml.bind:jaxb-api:2.3.0'
}
dependencies {
implementation("javax.xml.bind:jaxb-api:2.3.0")
}
If you are developing a Java library,
and the generated classes should be part of the library’s public API, then you should use the api configuration
instead.
|
Now, whenever the project’s sources are built, XJC will automatically be invoked to generate Java files and include them in the compilation.
Using Source Sets
Many Gradle Java projects use multiple
source sets. This
includes the main
source set for the project’s production code, as well as the test
source set for unit tests by
default, and can easily be extended with additional, custom source sets in a Gradle build script.
Support for Multiple Source Sets
The org.unbroken-dome.xjc
plugin adds XJC functionality for each source set in the project, both for existing
source sets and source sets that are added after the plugin is applied.
For each source set there will be a task of type XjcGenerate
that will do the actual work of invoking XJC for
the source set. You will rarely need to call this task directly, because the plugin already sets up task dependencies
for compileJava
so that the generated sources are ready before the compilation starts.
Customizing Paths
By default, all XJC input files for a source set are expected in the src/<name>/schema
directory, where <name>
is the name of the source set. For example,
-
put XJC input files for the
main
source set intosrc/main/schema
; -
put XJC input files for the
test
source set intosrc/test/schema
.
If you would like to change the default subdirectory name from schema
to something else, you can do this conveniently
by setting the xjcSrcDir
property on the project’s global xjc
extension. For example, to use the xjc
subdirectory
instead of schema
:
xjc {
srcDirName = 'xjc'
}
xjc {
srcDirName.set("xjc")
}
Each group of input files (schemas, binding customizations, catalogs, and URL sources) is also made available
on the source set as a
SourceDirectorySet
for further customization:
-
xjcSchema
for schema files -
xjcBinding
for binding customization files -
xjcUrl
for files listing remote schema URLs -
xjcCatalog
for catalog files
For example, to include another directory containing catalogs:
sourceSets {
main {
xjcCatalog.srcDir("custom/catalog/path")
}
}
sourceSets.named("main") {
xjcCatalog.srcDir("custom/catalog/path")
}
Remote Schema Locations
XJC supports processing schemas from remote locations, in addition to schemas stored in the local file system.
To use such remote schemas with the org.unbroken-dome.xjc
Gradle plugin, add a text file
with the extension .url
into your src/main/schema
directory. The .url
file should list
one remote schema URL per line.
For example, to use the xmldsig schema from w3.org, add the following file to your project:
https://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd
Note that this works best with schemas that are guaranteed to be immutable at the remote
location. If your .url
file or other input files do not change, the Gradle task will not
pick up changes from the remote schema location, and will still report as "up to date" in the
Gradle build.
To force a re-run of XJC even if your input files have not changed, use the --rerun-tasks
flag when invoking Gradle.
|
In most cases, it is still recommended to manually download the .xsd
files and put them into
your source directory. This will make your build less dependent on outside circumstances or network
connectivity, and thus more robust and reproducible.
XJC Versions
XJC has been around for a long time and comes in a variety of versions, including standard versions provided by Oracle and non-standard forks from third parties. They should usually correspond with the version of the JAXB API / runtime used, but it is also possible to use a newer XJC to generate code for use with an earlier JAXB target.
The org.unbroken-dome.xjc
plugin supports the standard XJC versions 2.2, 2.3 and 3.0. Other versions may work
as well but are not tested. The plugin will use XJC 2.3 by default if the version is not specified.
XJC 3.0 is currently in milestone status but since the interface to XJC has not really changed over the years, it should be safe to consider it stable. Nevertheless, the plugin will use XJC 2.3 as default until version 3.0 is released. |
Selecting an XJC Version
The easiest way to select an XJC version is to use the property xjcVersion
on the global xjc
extension.
The value should be one of the supported versions. This is a global setting because it is assumed that the
same XJC version will be used for each source set.
For example, to use XJC 3.0:
xjc {
xjcVersion = '3.0'
}
xjc {
xjcVersion.set("3.0")
}
When changing the XJC version, remember to also use an appropriate version of the JAXB API in your dependencies. The plugin doesn’t do this. |
Changing the version with the xjcVersion
property will effectively change the classpath that is used for
invoking XJC, with the following dependencies:
XJC Version | Classpath artifacts |
---|---|
2.2 |
|
2.3 |
|
3.0 |
|
Setting the XJC Tool Classpath Explicitly
You can also manually configure the classpath for invoking XJC by adding dependencies
to the xjcTool
configuration. As soon as this configuration contains any dependencies,
the defaults will back away, and the xjc.xjcVersion
property will have no effect.
For example:
dependencies {
xjcTool 'com.sun.xml.bind:jaxb-xjc:3.0.0-M4'
}
dependencies {
"xjcTool"("com.sun.xml.bind:jaxb-xjc:3.0.0-M4")
}
Controlling XJC Behavior
The command line version of XJC supports a number of arguments to fine-tune the code generation. The XJC Gradle plugin supports many of these as properties which can be set in the build script.
The following properties are available in the project-scoped xjc
block, and apply to all XJC invocations
(for all source sets):
Property | Description | Equivalent xjc CLI flag |
Default |
---|---|---|---|
|
The version of the JAXB specification to target. |
|
(use latest version) |
|
The locale to be used when running XJC. This may influence the language of documentation comments in XJC-generated files. |
(no equivalent) |
JVM default locale |
|
The encoding for generated files. |
|
|
|
Perform strict schema validation. |
|
|
|
Generate package-level annotations into |
|
|
|
Suppress the generation of a file header comment that includes some note and timestamp. |
|
|
|
Fix getter/setter generation to match the Bean introspection API. |
|
|
|
Generates content property for types with multiple |
|
|
|
Write-protect the generated Java source files. |
|
|
|
Enable JAXB vendor extensions. |
|
|
The following property is available for each source set:
Property | Description | Equivalent xjc CLI flag |
Default |
---|---|---|---|
|
The target package for XJC. |
|
(not set) |
Configuring XJC with Gradle Project Properties
As an alternative to configuring the above settings in your build script, you can use Gradle project
properties, either from a gradle.properties
file or passed on the command line using the -P
switch.
The property name correspond to the dot-separated path of the DSL properties.
Gradle properties are automatically picked up by subprojects in a multi-project build, so they are especially useful for configuring multiple projects at once.
For example:
xjc.xjcVersion=2.3
xjc.targetVersion=2.2
xjc.docLocale=en
xjc.strictCheck=false
xjc.enableIntrospection=true
xjc.contentForWildcard=true
xjc.verbosity=quiet
To selectively override properties for a Gradle build, use the -P
switch on the command line:
gradle build -Pxjc.verbosity=verbose
Using Gradle properties has lower precedence than explicitly setting them in your build script. |
Using XJC Plugins
XJC allows hooking into and extending the code-generation process by using plugins. A plugin might, for example, add
equals()
and toString()
methods into generated classes.
Some XJC plugins generate code that requires additional compile-time or runtime dependencies. Activating such plugins
might require that you add these dependencies to your Gradle dependency configurations, e.g. |
Specifying the plugin classpath
The plugin JARs must be on the classpath for the xjc
invocation. With the xjc Gradle plugin, you can do this very
comfortably by adding dependencies to the xjcClasspath
configuration.
For example, to use the JAXB2 Basics plugin:
dependencies {
xjcClasspath 'org.jvnet.jaxb2_commons:jaxb2-basics:0.12.0'
}
dependencies {
"xjcClasspath"("org.jvnet.jaxb2_commons:jaxb2-basics:0.12.0")
}
When using multiple source sets with XJC, there will be a separate XJC plugin classpath configuration for each source
set in the project. For the In addition, the plugin creates a configuration called |
Specifying extra arguments
Most XJC plugins need to be activated and/or configured using command-line arguments. You can specify these extra
arguments using the xjcExtraArgs
property on the source set. For example, to add the -Xequals
and -XtoString
arguments:
sourceSets {
main {
xjcExtraArgs.addAll '-Xequals', '-XhashCode', '-XtoString'
}
}
sourceSets.named("main") {
xjcExtraArgs.addAll("-Xequals", "-XhashCode", "-XtoString")
}
The plugin will automatically switch on the JAXB extension mode if there are parameters starting with -X
present, regardless of the extension
option.
The project-scoped For example, you could use a combination of the Groovy
Kotlin
|
Using Episodes
An episode file is a special file that contains information about the generated classes and can be imported by
subsequent xjc runs to re-use the generated code. If the episode file is placed in the "magic" location
META-INF/sun-jaxb.episode
inside a JAR file, it can be picked up automatically by dependent builds. This is
convenient when an import hierarchy of XML schemas should be reflected in a dependency hierarchy of JARs.
Producing an Episode File
To instruct XJC to produce an episode file, simply set the xjcGenerateEpisode
property on the source set to true
:
sourceSets {
main {
xjcGenerateEpisode = true
}
}
sourceSets.named("main") {
xjcGenerateEpisode.set(true)
}
The output of the XJC generation will now include an episode file at META-INF/sun-jaxb.episode
, which is set up
as an input to the source set’s resources
. As a consequence, this episode file will also end up in the JAR when
generated from the main
source set.
Consuming Episodes
Episode files are not consumed directly, but through JARs that contain them under META-INF/sun-jaxb.episode
.
To specify dependencies that contain episodes to be imported into the XJC build, add the libraries containing them
to the xjcEpisode
configuration:
dependencies {
xjcEpisode 'org.example:my-model:1.2.3'
}
dependencies {
"xjcEpisode"("org.example:my-model:1.2.3")
}
Each source set will get its own episode dependency configuration. For the main source set, this configuration
is called xjcEpisode ; for other source sets it will be called <name>XjcEpisode . For example, to specify episode
dependencies for the test source set, use the testXjcEpisode configuration.
|
These dependencies should resolve to JAR files; if they contain a META-INF/sun-jaxb.episode
entry it will be
imported by the current xjc
invocation. If they don’t contain such a file they are simply ignored, so it is safe
to pass a larger set of dependencies and have them scanned for episodes.
For example, you might simply want to scan the entire compileClasspath
for any JARs containing episodes:
dependencies {
xjcEpisode configurations['compileClasspath']
}
dependencies {
"xjcEpisode"(configurations["compileClasspath"])
}
Working with Catalogs
Catalogs can be used to map the URI of an imported schema (specified using <xsd:import>
) to an actual URL or file
from where it can be read. This is especially useful if the imported URI is only symbolic, or you cannot
(or do not want to) change the importing schema.
Catalog files may be written in any of the formats understood by the Apache Commons XML Resolver, including OASIS TR9401 and OASIS XML Catalogs.
Catalog files located at src/<sourceSetName>/schema
with the extension .cat
are picked up automatically. If your
files follow a different convention, you can modify the xjcCatalog
source directory set to suit your needs:
sourceSets {
main {
xjcCatalog {
// pick up catalogs from a custom directory
srcDir 'custom/catalog/path'
// use the .catalog file extension
include '**/*.catalog'
}
}
}
sourceSets.named("main") {
xjcCatalog {
// pick up catalogs from a custom directory
srcDir("custom/catalog/path")
// use the .catalog file extension
include("**/*.catalog")
}
}
In the catalog file, the following directives are most useful:
-
system
to map one URI to another; -
rewriteSystem
to map a group of URIs by exchanging a prefix
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<rewriteSystem
systemIdStartString="http://schemas.example.com/"
rewritePrefix="http://www.example.com/etc/schemas/"/>
</catalog>
The classpath:
and maven:
URI Schemes
This plugin supports two special URI schemes in catalogs, the classpath:
and maven:
scheme. They are inspired
(and largely compatible) with the [JAXB2 Maven Plugin](https://github.com/highsource/maven-jaxb2-plugin), in order
to simplify a migration from Maven to Gradle in projects that use XJC code generation.
The classpath:
URI scheme
The classpath:
scheme interprets the rest of the URI as the path to a classpath resource. This is especially
useful for multi-step code generation where a library JAR contains the schema, an episode file and generated code:
dependencies {
implementation 'com.example:my-model:1.2.3'
xjcEpisode configurations['compileClasspath']
}
dependencies {
implementation("com.example:my-model:1.2.3")
"xjcEpisode"(configurations["compileClasspath"])
}
Assuming the my-model
JAR contains an XSD resource at schemas/my-model.xsd
, you could write
the catalog file as follows:
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<rewriteSystem
systemIdStartString="http://schemas.example.com/"
rewritePrefix="classpath:schemas/"/>
</catalog>
Then, reference it in the importing schema:
<!-- The schemaLocation will be mapped to the JAR classpath resource thanks to the catalog -->
<xsd:import namespace="http://schemas.example.com/mymodel"
schemaLocation="http://schemas.example.com/my-model.xsd" />
All JARs in the special configuration xjcCatalogResolution
are taken into account, which inherits all dependencies
from compileClasspath
by default. You can add additional dependencies to this configuration to enable catalog
resolution from these artifacts.
When using multiple source sets with XJC, there will be a separate dependency configuration for each source set in
the project. For the In addition, the plugin creates a configuration called |
The maven:
URI scheme
The maven:
scheme works similar to the classpath:
scheme, but allows you to specify additional Maven coordinates
to filter the dependency. The URI syntax is:
maven:<groupId>:<artifactId>:<extension>:<classifier>:<version>!path/to/resource
where all parts are optional, and trailing colons may be omitted.
Note that in contrast to the Maven JAXB2 Plugin, the dependency is not resolved ad-hoc: it must still be
declared in the xjcCatalogResolution
configuration (or inherited through other configurations).
You can think of the maven:
scheme as an extension to classpath:
with a filter for the JARs to be searched
for resources. (In fact, classpath:
is defined as an alias for maven::!
.)
Migrating from Earlier Versions
Version 1.x of this plugin was centered around the XjcGenerate
task, which was automatically
created only for the main
source set.
In version 2.0, most of the configuration has moved to the xjc
DSL block in the project or the
source set extension (properties on the source set starting with xjc
), and XJC is enabled for all
source sets automatically. You will rarely need to touch the XjcGenerate
task directly with the new
plugin version.
In many XJC scenarios, no further configuration is necessary at all, beyond applying the plugin and adding a dependency to the JAXB API.
Since the interface to XJC has not changed much, the available properties have stayed mostly the
same, and it should not be difficult to figure out how to move them to the xjc
block or the source set.
Check the Controlling XJC Behavior section for a complete list of available properties.