7 Reasons I Do Not Use JAX-RS in Spring Boot Web Applications
A former JAX-RS aficionado discusses why he's switched his allegiance to Spring MVC for creating Spring Boot web apps, and gives some example code to help make his point.
Join the DZone community and get the full member experience.
Join For FreeJAX-RS has been around for the better part of a decade and I've spent a fair portion of my career coding with it. I've even created some custom in-house JAX-RS add-ons unofficially dubbed "JoeRS" by my coworkers.
But I've moved on. Now that I'm consistently using Spring Boot as the foundation for my applications, I've found that Spring MVC is just easier to work with than JAX-RS. This is not because JAX-RS is bad or because Spring Boot lacks strong JAX-RS support. In fact, I believe the Spring Boot team has gone above and beyond in their efforts to ensure JAX-RS works in Spring Boot. Regardless, here are seven reasons why I am not compelled to continue using JAX-RS within Spring Boot applications.
1. Spring MVC Results in More "Standard" Applications (Ironically)
Let me paraphrase a conversation I had with a fellow architect on a recent Spring Boot project.
- "Our approved tech stack requires that REST-style applications use JAX-RS."
- "Which version of the JAX-RS spec?"
- "2.1"
- "I'm surprised our enterprise tech stack is that up-to-date. Which implementation is approved?"
- "None in particular, as long as it's JAX-RS 2.1 compliant. I suggest we go with RESTEasy."
- "RESTEasy's latest stable release only supports JAX-RS 2.0. Also, start.spring.io doesn't offer a RESTEasy starter, only Jersey and Apache CXF."
- "CXF? I didn't know that was a JAX-RS implementation. Well, let's go with Jersey. It's the reference implementation, after all, so it must support JAX-RS 2.1."
- "Yes, but the most recent Spring Boot 1.5 Jersey Starter is at Jersey 2.25, which is only JAX-RS 2.0.1. Maybe we could do the POM exclusions thing in order to bring in Jersey 2.26 and JAX-RS 2.1."
- "That's annoying. Maybe we should stick with JAX-RS 2.0 for the time being and hope that a newer Spring Boot version drops soon that upgrades Jersey to 2.26. Besides, what does JAX-RS 2.1 have that's not in 2.0?"
- "I don't know--likely nothing that makes a difference for our application."
By this point, we had spent as much time discussing spec versions and implementation as we would have writing our initial Spring MVC Rest Controllers. And for what? So we could use some "standard" technology that prevents us from being locked into a specific vendor and/or ensures more consistency across developed applications?
Standards have their place, like in construction and plumbing, but I've found JavaEE/J2EE/EE4J/whatever-the-new-name-will-be "standards" to be quite leaky. I've never seen an .ear
port seamlessly from one application container to another. Nor have I been on a project where I could, say, replace org.jboss.resteasy:resteasy-jaxrs:3.1.4.Final
with org.glassfish.jersey.core:jersey-server:2.25.1
(two JAX-RS 2.0 implementations) in the POM, recompile, and run the application without issue. I know how to configure global JNDI resources in Tomcat; I don't know how to do that in Jetty, despite them both implementing the Servlet specification. Ultimately, the specification's implementation technology manifests itself distinctly within the application, introducing some degree of coupling with the specific implementation, diminishing the supposed value of a specification as a "standard."
For JAX-RS specifically, a Spring Boot application developer must first decide whether to use an available Spring Boot JAX-RS starter or manually integrate an alternate implementation like RESTEasy or Restlet. For those that opt for a starter, they must then decide between Apache CXF and Jersey. And once an implementation is selected, a developer must decide how to specifically integrate it into the application.
Consequently, Spring Boot applications using JAX-RS can be more varied in their implementation, integration, and configuration choices than applications that just use Spring MVC. Choosing to use Spring MVC spares developers from deliberating over these JAX-RS specific decisions.
2. I Don't Have to Think as Much About Versions
One of the greatest Spring Boot features (aside from the customizable banners, of course) is the spring-boot-dependencies
POM, which gets included in your POM by virtue of inheriting from spring-boot-starter-parent
. This single dependency manages versions of many various Java libraries, APIs, and Maven plugins, freeing me from the burdens of knowing, for example, if I'm including a recent version of Lombok or the Surefire plugin.
Spring Boot is allowing me to be more blissfully ignorant about the specific versions of dependencies I use. On the contrary, a JavaEE specification like JAX-RS essentially necessitates an awareness of version: the version of the JAX-RS API I wish to leverage and the version of the implementation library targeting that version, all in order to gain capabilities already available in Spring itself.
Of course, you could add the Jersey Spring Boot starter and be blissfully ignorant of the specific version of Jersey and the JAX-RS API in play, but that diminishes the "it's a standard" argument for using JAX-RS, in my opinion. If your primary argument for JAX-RS is "I'm just more familiar with it," then consider the rest of the reasons I favor Spring MVC.
3. I Don't Have to Think About Multiple IoC Containers
Who is responsible for managing the lifecycle of JAX-RS resource classes in a Spring Boot application? Spring? The JAX-RS implementation? Both? It depends.
Provided I'm only using the JAX-RS @Context
and various @***Param
annotations to inject information from the request, the JAX-RS implementation can manage the class's lifecycle with no need to involve Spring. Once I need to inject other managed dependencies, however, something more heavy duty is needed. JAX-RS 2 provides details for how a CDI container should work with JAX-RS resource classes. However, since we're using Spring, it seems superfluous to add some CDI implementation to the mix.
To have Spring manage the resource class lifecycle, I can annotate my resource classes with a @Component
annotation. But I must be mindful of scope. By default, JAX-RS resource classes are request-scoped whereas Spring components are application-scoped. Consider the following resource class:
@Component
@Path("/greeting")
public class GreetingResource {
@QueryParam("n")
private String name;
@GET
@Produces("text/plain")
public String greet() {
return "Hello, " + Optional.ofNullable(name).orElse("world") + "!";
}
}
Obviously, the class is a Spring bean by virtue of its @Component
annotation, and it is effectively a singleton since there is no Spring @Scope
annotation overriding the default scope. Notice, however, that the name
field gets its value from the request by virtue of its JAX-RS @QueryParam
annotation. This design poses a conundrum: if there are two simultaneous requests to /greeting?n=Bob
and /greeting?n=Alice
being serviced by a single instance of GreetingResource
, is there a race condition on writes to the name
field, possibly resulting in Alice's greeting being "Hello, Bob" or vice versa?
You bet there is!
Now, for this particular example class, the best option is to remove the @Component
annotation and let Jersey manage the resource class as I'm not autowiring in any other dependencies. But in most cases, I will want Spring to manage resource classes as beans, so the proper solution is to add a @Scope("request")
annotation to the class--or even better, not use JAX-RS at all and just use Spring MVC.
(Note: if I had attempted to write this resource class as a Spring MVC Controller with a @RequestParam
annotation--the equivalent of @QueryParam
--on the name
field, I would have gotten a compiler error because @RequestParam
can only be placed on method parameters. Maybe some consider this to be a limitation of Spring MVC in comparison with JAX-RS, but I consider it more of a safety feature.)
4. I Don't Have to Register JAX-RS Resource Classes
With Spring MVC, I can simply annotate my Controller classes with @RestController
and they're ready to go (assuming I've packaged them in a manner that allows them to be scanned). Using JAX-RS requires an additional step. For Jersey, specifically, the suggested approach is to create a ResourceConfig
extension bean that registers all resource classes.
@Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(Every.class);
register(Single.class);
register(ResourceClass.class);
register(Must.class);
register(Be.class);
register(Registered.class);
}
}
I've also found that I need to tweak my Application main class to extend SpringBootServletInitializer
, even if my packaging is jar
. Other JAX-RS implementations, including Jersey 1.x, have different registration approaches.
Not a huge deal, but still a minor annoyance that does not exist when using pure Spring MVC.
5. I Feel Less Guilty About Being Non-RESTful
JAX-RS stands for "Java API for RESTful Web Services" and uses RESTful terminology, particularly the word "resource." This is a negative in my opinion, not only because the term is so overloaded--in a Spring Boot project using JAX-RS, "resource" could refer to the key REST architectural abstraction, a JAX-RS resource class, a Spring Resource, or even a developer, frustratingly enough--but it presumes I'm building a truly RESTful API, replete with resources, representations, and hypermedia (oh my!).
Spring MVC uses nomenclature from the MVC pattern, instead (not surprisingly). This means I am free to implement more of a JSON RPC-style API without hypermedia links in my JSON responses if I so choose and Spring MVC will not judge me. I'm much more confident in my ability to adhere to the MVC pattern than the REST architecture style in a manner that would avoid the wrath of Roy Fielding.
6. Actuator Just Works
Apart from the customizable banners and aforementioned spring-boot-dependencies
POM, another great Spring Boot feature is the Actuator. By merely adding the actuator starter dependency to my POM, my application gains a number of useful monitoring and informational production-ready endpoints...except if I'm using JAX-RS.
This is a consequence of a) Actuator endpoints being implemented with Spring MVC and b) the Jersey dispatcher Servlet being mapped to path /
, thereby handling all of the requests to the would-be actuator URLs.
The resolution of this problem is straightforward, albeit annoying; include Spring MVC via spring-boot-starter-web
and map the Jersey and Spring MVC dispatcher Servlets to different paths.
But note that even after I get the Spring MVC-powered Actuator endpoints working alongside my application's JAX-RS resources, the /mappings
endpoint fails to display how the JAX-RS resource classes are mapped and /beans
will not display any that are not true Spring components (i.e. annotated with @Component
).
7. I Can Use MockMvc
MockMvc is a nice integration testing tool that enables me to test Spring MVC controllers without having to actually run an instance of the application, reducing the overall time it takes to execute the tests. MockMvc only works with Spring MVC, though; in order to integration test JAX-RS resources classes, you need to spin up instances of the application and use TestRestTemplate
or a library like RestAssured.
Conclusion
As I stated earlier, I have nothing against JAX-RS itself nor do I feel Spring Boot lacks good JAX-RS support, but I believe if an application developer is working with Spring Boot, there is no compelling reason to use JAX-RS instead of Spring MVC. If anyone actually knows of any good reason to go with JAX-RS, let me know.
Opinions expressed by DZone contributors are their own.
Comments