Error executing: wsimport
When setting up Java web services projects, there are a couple of principles that I really like to follow. First of all, I like to stick to open standards as much as possible. And I like to use up-to-date, but not bleeding-edge versions. I think this currently means Java EE 5, JAX-WS/JAXB 2.1, and Java SE 6: a well-known stack that is available on GlassFish, JBoss, WebLogic and many other servers. As a build tool, I think Maven should be preferred in most cases. And, since web services are all about interoperability, I believe in a start-from-wsdl approach: generate the Java from the contract, not the other way around. Finally, I think classes for value objects should have sensible implementations of equals, hashCode and toString, and that this applies to generated code as well.
Can these wishes all be fulfilled? In theory, this should be fairly easy. JAX-WS comes with the wsimport tool to implement the start-from-wsdl approach. A Maven plugin is available for that tool. Wsimport relies on JAXB’s xjc to perform schema compilation. There are a lot of plugins available for xjc, including the JAXB2 Basics plugins for generating equals, hashCode and toString.
In reality, it’s not that easy. Combining these tools is a bit of a challenge. In this post, I’ll discuss some of the problems that occur and show a solution.
Problem 1: The Maven wsimport plugin hides error information
In case anything goes wrong with the Maven wsimport plugin (org.codehaus.mojo:jaxws-maven-plugin:1.12), you are likely to see nothing more than “Error executing: wsimport”, followed by the wsimport command line arguments, in the Maven output. This provides no clue at all to the real problem. This is caused by an exception being caught without logging on line 293 of org.codehaus.mojo.jaxws.WsImportMojo. When debugging the problems described in this post, I recompiled the plugin with a stack trace dump at that point.
Problem 2: JAX-WS API 2.1 is messed up in the Maven repositories
The artifact javax.xml.ws:jaxws-api:2.1 is available from the Java.net Maven 1 repository, and from Maven Central. Unfortunately, these are different versions. Java.net has the correct jar file, having 36168 bytes. The wrong one on Maven Central has 33428 bytes. This is really bad for the reliability of your build. The build may run fine for one developer, and crash for the other, depending on the history of their local repository. This is exacly what Maven is supposed to prevent.
There is a solution to this: Maven Central also has javax.xml.ws:jaxws-api:2.1-1. This provides the correct jar file, known as 2.1 on Java.net. This artifact is unambigious. But a second problem pops up: the pom has a dependency on javax.jws:jsr181:1.0. On Maven Central, this is a pom without a jar. On Java.net, it doesn’t exist. This dependency should be excluded and replaced by the correct dependency on javax.jws:jsr181-api:1.0-MR1.
Problem 3: JAX-WS/JAXB API versions must match those of the JRE
J2SE 5.0 does not come with JAX-WS or JAXB. These libraries must simply be placed on the classpath, and everything works fine. Java SE 6 has the APIs and an implementation included. Java SE 6 started with the 2.0 APIs. From update 4, it has the 2.1 APIs. It is not a problem to use a newer version of the RI than is included with the JRE (say, 2.1.7 instead of 2.1.4); but the versions of the APIs must match (using 2.1 on a pre-update-4 JRE having 2.0 causes a problem). There is a workaround for this issue: copying the desired API jars to JAVA_HOME/jre/lib/endorsed. I really, really, dislike that. JREs are intended to be platforms shared by many applications, and messing around with them to get a particular application to work is a Bad Thing. This for me is one good reason not to use JAX-WS 2.2 yet.
Problem 4: JAXB2 plugins don’t work with Maven, Wsimport 2.1, and Java SE 6
Using the Maven wsimport plugin in combination with JAXB2 plugins works fine if you’re running J2SE 5.0. Not so on Java SE 6. This has nothing to do with the JAX-WS/JAXB API version issue mentioned earlier. It has to do with the way service implementations are loaded by jaxb-xjc. On Java SE 6, the since-1.6 class java.util.ServiceLoader will be used. In combination with a lot of classloader voodoo (it includes something called the ParallelWorldClassLoader), this creates a problem that causes plugins to fail, unless they are in a package with the magic prefix com.sun.tools.xjc. Dmitry Katsubo has made a workaround available that is based on renaming plugins. Although I’m pretty sure it works (I haven’t tried), I really don’t like my projects to depend on a library that is completely beyond the realm of regular, maintained, available-in-Maven libraries.
Problem 5: wsdl location
With wsimport, you can provide a location where the generated web service client will find the wsdl at run time. This location will be used if the client is instantiated with the no-arg constructor. Unfortunately, the code generated by wsimport 2.1.x calculates a “file:” URL for the wsdl. This causes a hard reference to the local filesystem, and makes the no-arg constructor essentially unusable. As a workaround, there is a constructor that allows providing a URL. This is clumsy by itself, but to make matters worse, this constructor also wants a complete QName as a second argument, which further increases the amount of code needed for instantiating the client. This is fixed in wsimport 2.2, but not (yet) in 2.1.x.
Solution
How to achieve our goals, work around these problems, and still have a robust, portable build? I chose a solution based on the following ideas:
- Don’t use the Maven wsimport plugin. This gets rid of problems 1 and 4. Instead, run wsimport from Ant, and Ant from Maven. Specifically, I’m using the Maven AntRun plugin to run a small ant script (embedded in the pom.xml) that runs Ant’s Java task to start Ant in a different JVM, running an external Ant build.xml. While this may seem complex, it actually provides an elegant way to manage the dependencies and the classpath completely through Maven. Therefore, the Maven robustness and portability remains intact.
- Because there is no Maven plugin involved in source generation, Maven doesn’t know that there are generated Java files to be compiled. Compensate for this by using the build-helper-maven-plugin.
- Use jaxws-tools 2.2 (to get rid of problem 5), but generate source code for version 2.1, so 2.1 can be used at run-time and problem 3 doesn’t occur.
- Since I’m using Java SE 6, JAX-WS 2.1 is included and problem 2 can simply be ignored. When using J2SE 5.0, the problem should be dealt with by carefully excluding the problematic dependencies and including alternative ones.
These ideas are implemented in the Maven pom.xml and an additional Ant build.xml.
In pom.xml, we need a dependency on the JAXB2 Basics runtime, otherwise our generated code won’t compile:
<dependency> <groupId>org.jvnet.jaxb2_commons</groupId> <artifactId>jaxb2-basics-runtime</artifactId> </dependency>
We’ll let Maven set up a classpath that includes Ant, JAX-WS tools 2.2, and the JAXB2 Basics plugins. This will be the plugin classpath of the AntRun plugin. From the Ant script, we start a new JVM with exactly that classpath. In this way, classpath management remains in Maven’s control completely:
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>generate-sources</phase>
<configuration>
<tasks>
<property name="plugin_classpath" refid="maven.plugin.classpath"/>
<java classname="org.apache.tools.ant.launch.Launcher"
fork="true" failonerror="true">
<classpath>
<pathelement path="${plugin_classpath}"/>
</classpath>
</java>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>0.5.2</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-tools</artifactId>
<version>2.2.0.2</version>
</dependency>
</dependencies>
</plugin>
Next to Maven’s pom.xml, we use an Ant build.xml that executes wsimport. In the configuration, we make sure that wsimport activates the JAXB2 plugins when calling xjc (-Xequals etc.). The “target” parameter makes sure we’re still using JAX-WS 2.1. The “nocompile” flag makes sure we only generate Java: actual compilation is done through Maven. The paths are chosen so as to comply with the Maven standard:
<project name="wsimport" default="wsimport" basedir=".">
<taskdef name="wsimport" classname="com.sun.tools.ws.ant.WsImport" />
<target name="wsimport">
<echo message="Starting wsimport"/>
<mkdir dir="target/generated-sources/jaxws"/>
<wsimport
wsdl="src/main/resources/wsdl/demo.wsdl"
sourcedestdir="target/generated-sources/jaxws"
extension="true"
verbose="true"
target="2.1"
xnocompile="true"
wsdlLocation="/wsdl/demo.wsdl">
<xjcarg value="-Xequals"/>
<xjcarg value="-XhashCode"/>
<xjcarg value="-XtoString"/>
</wsimport>
</target>
</project>
Finally, we’ll need an addition to pom.xml to tell Maven about the generated sources:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>target/generated-sources/jaxws</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
Comments are closed.
I had absolutely the same issues. Your article helped me a lot. Thanks.
Thank you for nice alternative. I’ve put a backlink to this post.
Another reason why I have to bundle everything in one is that some very nice plugins (like jaxb-xew-plugin, jaxb-fluent-api-plugin) are not available in Maven2 repository. So you have to either put plugin jars or all-in-one bundle in your basket
Point (5) that you’ve mentioned is really a pain for me now. Great that you’ve mentioned all together!
FYI, This works with maven 2.2, jaxws-maven-plugin from codehaus version 1.12 when adding jaxws dependencies to the plugin. Oh, jrockit java 1.6 also
This works with java 1.6, maven 2.2, jaxws-maven-plugin 1.12 from codehaus when upgrading the jdk from jaxws 2.1.4 to 2.2.1. No files were placed in the endorsed directory.
You just saved me hours of googling and quite some hair. Couldn’t understand why the plugin wouldn’t give me details about the error…