JBoss Modules Suck, It's Impossible To Use Custom Resteasy/JAX-RS Under JBoss 7
Since JBoss EAP 6.1 / AS 7.2.0 is modular and you can exclude what modules are visible to your webapp, you would expect it to be easy to ignore the built-in implementation of JAX-RS (Rest Easy 2.3.6) and use a custom one (3.0.6). However, sadly, this is not the case. You are stuck with what the official guide suggests, i.e. upgrading Rest Easy globally - provided that no other webapp running on the server becomes broken by the upgrade.
This should be enough to exclude the built-in Rest Easy and be able to use a version included in the webapp:
However it is far from working. This nearly does the job (though few of the exclusions might be unnecessary):
However, only nearly. The problem is that the exclusion of javax.ws.rs.api has no effect. It seems as the core Java EE APIs cannot be excluded. Dead end.
BTW, this are my final jax-rs related dependencies:
- fixed likely by adding
- fixed likely by adding
- fixed likely by adding more of the RestEasy/Jackson modules to the exclusion list
- this is the ultimate one that cannot be fixed; the problem is that
Lessons learned: Application servers with the plenty of out-of-the-box, well-integrated (?) functionality seem attractive but when you run into conflicting libraries and classloading issues, their value diminishes rapidly. Starting with something simple that you control fully, such as Jettty, is perhaps in the long run a better solution. Also, running multiple webapps on the same server was perhaps smart in 2000 but is not worth the pain nowadays. We have plenty of disk space and memory so reuse of libraries is unimportant and the ability to manage global settings for all apps at one place has certainly better alternatives. Microservices FTW!
Update: As Yannick has pointed out, the conclusion seems too general and unjustified. That is because I have arrived to it already before and this problem with JBoss serves only as another confirmation.
Update 2: Bill Burke has proposed a solution, see below.
I needed even more time to find out that while core parts of Rest Easy are available automatically to the application, I needed to add <module name="org.jboss.resteasy.resteasy-jackson2-provider" services="import"/> to jboss-deployment-structure.xml to use Jackson 2 (the services=import is crucial, without it the Jackson2 is ignored by Rest Easy and it will use the Jackson 1 instead, leading to confusing failures to deserialize some JSON).
I can blame all the wasted time on my lack of deeper knowledge of JBoss and Rest Easy. But for me, it is another argument for preferring simpler solutions that I control fully, such as a jetty-based stack.
This should be enough to exclude the built-in Rest Easy and be able to use a version included in the webapp:
<!-- jboss-deployment-structure.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
<deployment>
<exclude-subsystems>
<subsystem name="resteasy"/>
</exclude-subsystems>
</deployment>
</jboss-deployment-structure>
However it is far from working. This nearly does the job (though few of the exclusions might be unnecessary):
<!-- jboss-deployment-structure.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
<deployment>
<exclude-subsystems>
<subsystem name="resteasy"/>
</exclude-subsystems>
<exclusions>
<module name="org.apache.log4j" />
<module name="org.apache.commons.logging"/>
<module name="org.jboss.as.jaxrs"/>
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
<module name="org.jboss.resteasy.resteasy-cdi"/>
<module name="org.jboss.resteasy.jackson-provider"/>
<module name="org.jboss.resteasy.resteasy-atom-provider"/>
<module name="org.jboss.resteasy.resteasy-hibernatevalidator-provider"/>
<module name="org.jboss.resteasy.resteasy-jaxb-provider"/>
<module name="org.jboss.resteasy.resteasy-jettison-provider"/>
<module name="org.jboss.resteasy.resteasy-jsapi"/>
<module name="org.jboss.resteasy.resteasy-multipart-provider"/>
<module name="org.jboss.resteasy.resteasy-yaml-provider"/>
<module name="org.codehaus.jackson.jackson-core-asl"/>
<module name="org.codehaus.jackson.jackson-jaxrs"/>
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
<module name="org.codehaus.jackson.jackson-xc"/>
<module name="org.codehaus.jettison"/>
<module name="javax.ws.rs.api"/>
</exclusions>
</deployment>
</jboss-deployment-structure>
However, only nearly. The problem is that the exclusion of javax.ws.rs.api has no effect. It seems as the core Java EE APIs cannot be excluded. Dead end.
BTW, this are my final jax-rs related dependencies:
// resteasyVersion = '3.0.6.Final'
compile group: 'org.jboss.resteasy', name: 'jaxrs-api', version: resteasyVersion
compile group: 'org.jboss.resteasy', name: 'resteasy-jaxrs', version: resteasyVersion
compile group: 'org.jboss.resteasy', name: 'resteasy-jackson2-provider', version: resteasyVersion // JSONP
compile group: 'org.jboss.resteasy', name: 'async-http-servlet-3.0', version: resteasyVersion // Required at runtime
compile group: 'org.jboss.resteasy', name: 'resteasy-servlet-initializer', version: resteasyVersion // Required at runtime
An approximate history of failed attempts
I do not remember anymore exactly all the dead ends I went through but here is an approximate overview of the exceptions I got at deployment or runtime.java.lang.ClassNotFoundException: org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher
- fixed likely by adding
org.jboss.resteasy:async-http-servlet-3.0:3.0.6.Final
to the dependenciesjava.lang.ClassCastException: myapp.rs.RestApplication cannot be cast to javax.servlet.Servlet
- fixed likely by adding
org.jboss.resteasy:resteasy-servlet-initializer:3.0.6.Final
to the dependenciesjava.lang.NoSuchMethodError: org.jboss.resteasy.spi.ResteasyProviderFactory.<init>(Lorg/jboss/resteasy/spi/ResteasyProviderFactory;)V
- fixed likely by adding more of the RestEasy/Jackson modules to the exclusion list
java.lang.NoSuchMethodError: org.jboss.resteasy.specimpl.BuiltResponse.getHeaders()Ljavax/ws/rs/core/MultivaluedMap;
- this is the ultimate one that cannot be fixed; the problem is that
BuiltResponse
from resteasy-jaxrs
inherits from javax.ws.rs.core.Response
however the version of this class from jaxrs-api-3.0.6.Final.jar is ignored in favour of Response
from JAX-RS 1.1 from the javax.ws.rs.api
module (/jboss-eap-6.1.0/modules/system/layers/base/javax/ws/rs/api/main/jboss-jaxrs-api_1.1_spec-1.0.1.Final-redhat-2.jar
), which lacks the getHeaders
method and, as mentioned, cannot be excluded. (Thanks to allprog for hinting at this confilct!)
Conclusion
The only way to use a newer JAX-RS is to upgrade the JBoss modules. If that would break some other webapps, you are stuck.Lessons learned: Application servers with the plenty of out-of-the-box, well-integrated (?) functionality seem attractive but when you run into conflicting libraries and classloading issues, their value diminishes rapidly. Starting with something simple that you control fully, such as Jettty, is perhaps in the long run a better solution. Also, running multiple webapps on the same server was perhaps smart in 2000 but is not worth the pain nowadays. We have plenty of disk space and memory so reuse of libraries is unimportant and the ability to manage global settings for all apps at one place has certainly better alternatives. Microservices FTW!
Update: As Yannick has pointed out, the conclusion seems too general and unjustified. That is because I have arrived to it already before and this problem with JBoss serves only as another confirmation.
Update 2: Bill Burke has proposed a solution, see below.
Follow up
I've updated Rest Easy in JBoss, however that proved not to be enough. It took me a while to find out that I must make sure to exclude some/all jboss/resteasy/javax dependencies from my WAR (with the important exception ofresteasy-servlet-initializer
) - having them there led sometimes to NoClassDefFoundException for a class that was there, a typical classloader hell.I needed even more time to find out that while core parts of Rest Easy are available automatically to the application, I needed to add <module name="org.jboss.resteasy.resteasy-jackson2-provider" services="import"/> to jboss-deployment-structure.xml to use Jackson 2 (the services=import is crucial, without it the Jackson2 is ignored by Rest Easy and it will use the Jackson 1 instead, leading to confusing failures to deserialize some JSON).
I can blame all the wasted time on my lack of deeper knowledge of JBoss and Rest Easy. But for me, it is another argument for preferring simpler solutions that I control fully, such as a jetty-based stack.