A Gradle plugin for generating Java classes from WSDL files through CXF.
- The plugin requires Gradle 7.6 or later. (Tested with Gradle 7, 8 and 9.)
- For using the Jakarta namespace (default), the plugin requires Java 17 or greater. For the older javax namespace, you can use Java 8, 11 or 17.
- It supports the Gradle build cache (enabled by setting
org.gradle.caching=truein your gradle.properties file). - It supports the Gradle configuration cache (enabled by setting
org.gradle.configuration-cache=truein your gradle.properties file). - It supports project relocation for the build cache (e.g. you move your project to a new path, or make a new copy/clone of it).
This is especially useful in a CI context, where you might clone PRs and/or branches for a repository in their own locations.
Note, however, that there is an issue in an Apache library called
org.apache.ws.xmlschema:xmlschema-corethat might break this on older CXF versions - see below for details. - It supports parallel execution (enabled with
org.gradle.parallel=truein your gradle.properties file), though it does not itself run anything in parallel. - It supports isolated projects (enabled with
org.gradle.unsafe.isolated-projects=trueon Gradle 8.8, ororg.gradle.isolated-projects=trueon Gradle 9.7+, in your gradle.properties file).
Apply the plugin ID "com.github.bjornvester.wsdl2java" as specified in the Gradle Plugin portal page, e.g. like this:
plugins {
id("com.github.bjornvester.wsdl2java") version "2.0.4"
}Put your WSDL and referenced XSD files somewhere in your src/main/resources directory. By default, the plugin will create Java classes for all the WSDL files in the resource directory.
The generated code will by default end up in the directory build/generated/sources/wsdl2java/java.
You can configure the plugin using the "wsdl2java" extension like this:
wsdl2java {
// Set properties here...
}Here is a list of all available properties:
| Property | Type | Default | Description |
|---|---|---|---|
| wsdlDir | DirectoryProperty | "$projectDir/src /main/resources" |
The directory holding the WSDL and referenced XSD files to compile. |
| includes | ListProperty<String> | ["**/*.wsdl"] | Inclusion filters (Ant style) for which WSDLs to include. The patterns are relative to wsdlDir. |
| includesWithOptions | Map<String, List> | [not set] | Inclusion filters like above, but with individual options. See below. |
| generatedSourceDir | DirectoryProperty | "$buildDir/generated /sources/wsdl2java/java" |
The output directory for the generated Java sources. Note that it will be deleted when running XJC. |
| bindingFile | RegularFileProperty | [not set] | A binding file to use in the schema compiler. |
| useJakarta | Provider<Boolean> | true | Set to use the jakarta namespace. If false, uses the javax namespace. This value also determines the default version of CXF. |
| cxfVersion | Provider<String> | "4.2.3" for jakarta / 3.5.11 for javax | The version of CXF to use. Use a version >= 4 for jakarta and below for javax. Note that the javax default is the last Java 8 compatible release (CXF 3.6+ requires Java 11). |
| verbose | Provider<Boolean> | [not set] | Enables verbose output from CXF. If not set, it will be enabled only on the info logging level. |
| markGenerated | Provider<Boolean> | false | Adds the @Generated annotation to the generated sources. See below for details as there are some gotchas with this. |
| generatedStyle | Provider<String> | "default" | If using the @Generated annotation, selects which of the three @Generated annotations to use. See below for details. |
| suppressGeneratedDate | Provider<Boolean> | true | Suppresses generating dates in CXF. Default is true to support reproducible builds and to work with the build cache. |
| packageName | Provider<String> | [not set] | Sets the package name for the generated sources |
| options | ListProperty<String> | [empty] | Additional options to pass to the tool. See here for details. |
| addCompilationDependencies | Provider<Boolean> | true | Adds dependencies to the implementation configuration for compiling the generated sources. These include jakarta.xml.ws:jakarta.xml.ws-api, jakarta.jws:jakarta.jws-api and possibly jakarta.annotation:jakarta.annotation-api. |
| useProcessIsolation | Provider<Boolean> | true | Runs the code generation in a separate process. Set to false to use classloader isolation instead, which allows attaching a debugger to the worker. |
You can specify the version of CXF used for code generation like this:
wsdl2java {
cxfVersion.set("3.6.12")
}Note that:
- CXF version 4.2.x: Jakarta EE 11 (jakarta namespace). JDK 17 baseline.
- CXF version 4.1.x: Jakarta EE 10 (jakarta namespace). JDK 17 baseline.
- CXF version 4.0.x: Jakarta EE 9.1 (jakarta namespace). JDK 11 baseline.
- CXF version 3.6.x: Java EE 8 (javax namespace). JDK 11 baseline.
- CXF version 3.5.x: Java EE 8 (javax namespace). JDK 8 baseline.
If you use Spring Boot, you should match the alignment to Jakarta EE:
- Spring Boot version 4.0-4.2: Jakarta EE 11
- Spring Boot version 3.2-3.5: Jakarta EE 10
- Spring Boot version 3.0-3.1: Jakarta EE 9.1
- Spring Boot version 2.0-2.7: Java EE 8
If you are using CXF in your project as well, you can align the version by:
dependencies {
implementation(platform("org.apache.cxf:cxf-bom:4.2.3")) // CXF used for the project source code (and not the plugin)
}By default, the plugin will find all WSDL files in the wsdlDir directory, which defaults to src/main/resources.
It is important that if you change this, you change it to a folder that contain all resources (e.g. both WSDL and XSDs).
Otherwise, if you make changes to files outside this folder, Gradle will not see them and thus might consider the task
up-to-date.
The plugin will set the wsdlLocation property of the @WebServiceClient to the path for the WSDL file relative to
the wsdlDir directory.
If you change wsdlDir in a way where this no longer makes sense, you need to set the option yourself.
If you have multiple WSDL files, see the section on additional options.
If you have multiple WSDL files and want to only run the tool on some of them, you can use the includes property.
The patterns are Ant style and relative to the wsdlDir directory.
Example:
// Only if different from the default 'src/main/resources'
wsdlDir.set(layout.projectDirectory.dir("src/main/wsdls"))
// For Kotlin DSL
includes.set(
listOf(
"MyFirstService.wsdl",
"MySecondService.wsdl"
)
)// For Groovy DSL
includes = [
"MyFirstService.wsdl",
"MySecondService.wsdl"
]Besides the options given in the wsdl2java extension, you can provide additional options directly to CXF (and XJC)
through the options property:
// For Kotlin DSL
options.set(
listOf(
"-xjc-no-header",
"-xjc-npa"
)
)
// For Groovy DSL
options = [
"-xjc-no-header",
"-xjc-npa"
]See here for the available options.
It is possible to pass options for individual WSDL files.
This is important especially if you need to explicitly configure the wsdlLocation option, as it doesn't make sense to have
the same location in all files.
(But note that if you leave it out, the plugin will guess it based on the file location.)
// For Kotlin DSL
includesWithOptions.set(
mapOf(
"**/ServiceA.wsdl" to listOf("-wsdlLocation", "https://example.com/service-a?wsdl"),
"**/ServiceB.wsdl" to listOf("-wsdlLocation", "https://example.com/service-b?wsdl")
)
)// For Groovy DSL
includesWithOptions = [
"**/ServiceA.wsdl": ["-wsdlLocation", "https://example.com/service-a?wsdl"],
"**/ServiceB.wsdl": ["-wsdlLocation", "https://example.com/service-b?wsdl"]
]You can optionally specify the directory for the generated source through the generatedSourceDir property, which
defaults to $buildDir/generated/sources/wsdl2java/java.
Example:
wsdl2java {
generatedSourceDir.set(layout.projectDirectory.dir("src/generated/wsdl2java"))
}Note that the directory will be wiped completely on each run, so don't put other source files in it.
A binding file can be added like this:
wsdl2java {
bindingFile.set(layout.projectDirectory.file("src/main/bindings/binding.xjb"))
}If you get a warning on maximum enum member size, you can raise the maximum like this:
<jxb:bindings xmlns:jxb="https://jakarta.ee/xml/ns/jaxb"
jaxb:version="3.0">
<!-- Raise theEnumMemberSizeCap limit -->
<jxb:bindings>
<jxb:globalBindings typesafeEnumMaxMembers="2000"/>
</jxb:bindings>
</jxb:bindings>If you are using the older javax namespace instead of jakarta, set the jxb attribute to http://java.sun.com/xml/ns/jaxb instead and the version to 2.1.
If you like to use the Java Date/Time API instead of the more clunky GregorianCalendar class, you can use threeten-jaxb library with a binding file like example below.
dependencies {
implementation("io.github.threeten-jaxb:threeten-jaxb-core:2.1.0")
}
wsdl2java {
bindingFile.set(layout.projectDirectory.file("src/main/bindings/bindings.xjb"))
}<bindings xmlns="https://jakarta.ee/xml/ns/jaxb"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
version="3.0"
extensionBindingPrefixes="xjc">
<globalBindings>
<xjc:javaType name="java.time.LocalDate" xmlType="xs:date" adapter="io.github.threetenjaxb.core.LocalDateXmlAdapter"/>
<xjc:javaType name="java.time.LocalDateTime" xmlType="xs:dateTime" adapter="io.github.threetenjaxb.core.LocalDateTimeXmlAdapter"/>
<xjc:javaType name="java.time.YearMonth" xmlType="xs:gYearMonth" adapter="io.github.threetenjaxb.core.YearMonthXmlAdapter"/>
<xjc:javaType name="java.time.Duration" xmlType="xs:duration" adapter="io.github.threetenjaxb.core.DurationXmlAdapter"/>
<xjc:javaType name="java.time.OffsetTime" xmlType="xs:time" adapter="io.github.threetenjaxb.core.OffsetTimeXmlAdapter"/>
<xjc:javaType name="java.time.OffsetDateTime" xmlType="xs:dateTime" adapter="io.github.threetenjaxb.core.OffsetDateTimeXmlAdapter"/>
</globalBindings>
</bindings>In the above, for javax, use version 1.2 and the binding attributes to xmlns="http://java.sun.com/xml/ns/jaxb" version="2.1".
Note that at the moment, it is not possible to specify more than one binding file in the extension. If you require this,
use the -b option.
The package name for the generated resources can be configured with the packageName field:
wsdl2java {
packageName.set("com.github.bjornvester.wsdl2java.group1")
}
If your WSDL files include non-ANSI characters, you should set the corresponding file encoding in your gradle.properties file. E.g.:
org.gradle.jvmargs=-Dfile.encoding=UTF-8If you are on a POSIX operating system (e.g., Linux), you may, in addition to this, need to set your operating system locale to one that supports your encoding. Otherwise, Java (and therefore also Gradle and CXF) may not be able to create files with names outside of what your default locale supports. Especially some Docker images, like the Java ECR images from AWS, are by default set to a locale supporting ASCII only. If this is the case for you, and you want to use UTF-8, you could export an environment variable like this:
export LANG=C.UTF-8To use the older javax namespace in the generated files, set the useJakarta property to false.
wsdl2java {
useJakarta.set(false)
}To use third party plugins for the underlying XJC tool, supply the relevant dependencies to the xjcPlugins
configuration.
Then set the plugin options through the options property.
For example, to use the "Equals" and "Hashcode" plugins from the
JAXB Tools project, configure the following.
The project supports both the javax and jakarta namespaces - you just need to select the version that matches
your JAXB version (and thus your CXF version):
For the jakarta namespace (CXF 4 and later, JAXB 3.0+), use the jaxb-plugins artifacts:
dependencies {
implementation("org.jvnet.jaxb:jaxb-plugins-runtime:4.0.9")
xjcPlugins("org.jvnet.jaxb:jaxb-plugins:4.0.9")
}
wsdl2java {
options.addAll("-xjc-Xequals", "-xjc-XhashCode")
}For the older javax namespace (CXF 3, JAXB 2.3), use the jaxb2-basics artifacts:
dependencies {
implementation("org.jvnet.jaxb:jaxb2-basics-runtime:2.0.15")
xjcPlugins("org.jvnet.jaxb:jaxb2-basics:2.0.15")
}
wsdl2java {
options.addAll("-xjc-Xequals", "-xjc-XhashCode")
}CXF (and the underlying XJC tool) can add a @Generated annotation to the generated source code.
This is a source annotation useful for marking classes as generated by a tool.
It can also be used by some static code analytic tools to skip these classes.
You can set it by the markGenerated configuration:
wsdl2java {
markGenerated.set(true)
}While useful in theory, there are some gotchas with this annotation.
One is that it is a source code annotation and thus won't work with tools that work with byte code (like JaCoCo).
Another is that there are actually three @Generated annotations.
- The first is
javax.annotation.Generatedand is available in the JDK up to and including Java 8. It is also included in thejavax.annotation:javax.annotation-apidependency. - The second is
javax.annotation.processing.Generatedand is available in the JDK from Java 9. - The third is
jakarta.annotation.Generatedand is included in thejakarta.annotation:jakarta.annotation-apidependency.
CXF 3 (javax) uses the first by default, and CXF 4 (jakarta) uses the third by default.
Because your tools may only support a particular version of the @Generated annotation, you can select which one through the generatedStyle configuration.
Supported values are: default, jdk8, jdk9 and jakarta.
Example:
dependencies {
// The dependency below is only needed if using the Java 8 version of @Generated (through "jdk8") on Java 9 or later
implementation("javax.annotation:javax.annotation-api:1.3.2")
}
wsdl2java {
markGenerated.set(true)
generatedStyle.set("jdk8")
}The plugin will also, by default, strip any timestamps from the @Generated annotations.
This is to support reproducible builds and the Gradle cache.
This can be disabled by the suppressGeneratedDate configuration:
wsdl2java {
suppressGeneratedDate.set(false)
}If the includesWithOptions is not enough, you can also make groups of WSDL files, each with its own configuration.
This is done like this:
wsdl2java {
groups {
register("group1") {
includes.set(listOf("**/HelloWorldAService.wsdl"))
options.set(listOf("-wsdlLocation", "MyLocationA"))
packageName.set("com.github.bjornvester.wsdl2java.group1")
}
register("group2") {
includes.set(listOf("**/HelloWorldBService.wsdl"))
options.set(listOf("-wsdlLocation", "MyLocationB"))
packageName.set("com.github.bjornvester.wsdl2java.group2")
}
}
}To avoid generating resources with the same name but different content (for instance like the generated ObjectFactory class), you should make sure each group uses its own package name.
The plugin will add the following dependencies to your implementation configuration (for the default jakarta namespace):
jakarta.xml.ws:jakarta.xml.ws-api:3.0.1
jakarta.jws:jakarta.jws-api:3.0.0
For the javax namespace, the versions are jakarta.xml.ws:jakarta.xml.ws-api:2.3.3 and jakarta.jws:jakarta.jws-api:1.1.1.
If you enable the @Generated annotation (with a generatedStyle other than jdk9), a third dependency, jakarta.annotation:jakarta.annotation-api, is also added.
These are required to compile the generated code.
However, depending on your runtime platform, you may want to exclude them and instead either put them in the compileOnly configuration, or use whatever libraries that are
provided by the platform.
There are full examples available in the integration-test directory.
If you need to compile additional XML schemas (xsd files) not directly referenced by the wsdl files, you can use the com.github.bjornvester.xjc plugin in addition.
The CXF tool will overwrite generated classes from multiple WSDL files if they have the same qualified name.
Especially the ObjectFactory class might be overwritten, which is very annoying.
When CXF generates an ObjectFactory class, the order of the methods is not deterministic but depends on the absolute path of the input files.
This may cause misses in the Gradle build cache, which may propagate to downstream projects, resulting in long build times.
This was fixed in org.apache.ws.xmlschema:xmlschema-core 2.3.1, which is bundled by the default CXF versions (3.5.11 and 4.2.3).
If you use an older CXF version, you may still be affected.
See https://issues.apache.org/jira/browse/XMLSCHEMA-65 for more information.
I often feel a bit stretched out in terms of maintaining this and my other projects. For that reason, I might not respond to issues and PRs very often.