Using Spring 3.0 MVC for RESTful web services (rebuttal)

Update Mar.04 Thanks to @ewolff some of the points described below are now official feature requests. One (SPR-6928) is actually scheduled in Spring 3.1 (cool!). I’ve updated the post and added all open tickets. Please vote!

This post is somewhat a response to InfoQ’s Comparison of Spring MVC and JAX-RS.
Recently I have completed a migration from a JAX-RS implementation of a web service to Spring 3.0 MVC annotation-based @Controllers. The aforementioned post on InfoQ was published a few days after my migration so I’m dumping below the list of problems I had, along with solutions.

Full list of issues:


Same relative paths in multiple @Controllers not supported
Consider two Controllers where I use a versioned URL and a web.xml file that uses two URL mappings:

@Controller
public class AdminController {
   @RequestMapping("/v1/{userId}")
   public SomeResponse showUserDetails(String userId) {
      ...
   }
}

@Controller
public class UserController {
   @RequestMapping("/v1/{userId}")
   public SomeOtherResponse showUserStreamtring userId) {
      ...
   }
}
In web.xml:
	<servlet-mapping>
	  <servlet-name>public-api</servlet-name>
	  <url-pattern>/public</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
	  <servlet-name>admin-api</servlet-name>
	  <url-pattern>/admin</url-pattern>
	</servlet-mapping>


Here I want to implement handling for /admin/v1/{userId} as well as /user/v1/{userId}. Each controller serves a different purpose (admin and public) and I want to keep the servlet application contexts separate for each servlet mapping (for instance add authentication and different view resolvers to all /admin/* URLs).
Deploying this configuration barfs the following exception:

java.lang.IllegalStateException: Cannot map handler [UserController] to URL path 
[/v1/{userId}]: There is already handler [com.test.controller. UserController@6b177115] 
mapped.

I would like Spring MVC to account for web.xml servlet mappings before deciding that two identical @RequestMappings in different controllers refer to the same URI. My solution was to have all @RequestMappings specifying the full path and resorting to a custom view to work around this issue).


@ExceptionHandler is controller-centric
RESTful web services respond with the usual HTTP responses (200s, 400s, 500s, etc.). These responses are being mapped from application exceptions and it only makes sense to specify the same @ExceptionHandler for all @Controllers since most (if not all) exceptions handled by them are for the same business domain. Currently SpringMVC only supports declaring and @ExceptionHander per controller and not globally per webapp. This forces the implementors to extend their @Controller classes from a common one where the @ExceptionHandler is specified or, to use composition and delegate exception handling to one service which leads to duplication. Both are ugly. There are several ways to deal with exceptions (check SPR-5622; please guys, add this to the main Spring documentation) but ideally I’d like to see a global scoped handler like @ExceptionHandler(scope=APPLICATION) where I can remap once framework exceptions to more suitable ones that translate into proper error codes (like working around SPR-5622 like issues).


Standard content negotiation can’t respond with a fixed response type (SPR-6937)
Here’s a simple requirement: Implement an RSS feed that serves a user’s activity stream.
Here’s another requirement (make that best practice):

Service methods should have as arguments primitives, wrappers or other POJOs and
 respond with void, primitives, wrappers or other POJOs.

I want testability, readability and portability for service classes and using framework objects for arguments/return objects adds unacceptable clutter.
As such, no framework objects are allowed as arguments or return types in a @Controller and in this case I don’t want to work with ModelAndViews just to be able to select the desired View.
Since this new requirement (add RSS view) belongs to the user domain, I want to add a method in the UserController class that returns a POJO which should be always unmarshalled into RSS regardless of request content type (there goes ContentNegotiationViewResolver). The same controller has other methods returning POJO view objects that I want marshalled into JSON or plain text response body and an HTTP response status. This is already available in RESTEasy via the elegant Produces annotation. The same RESTful controller should respond with different response types for different requests since I want to keep its domain centered around the user (and not response types).

...
public class UserController {
...
   @RequestHandler("/rss/{userId}")
   public SomePOJO getRssFeed(String userId) {
      ...
      //returns a POJO that should always be resolved by an RSS view
   }
   
   @RequestMapping("/activity/{userId}")
   public SomeOtherPOJO showUserStream(String userId) {
      ...
      //return POJO that should always be resolved by a JSON view
   }
...
}

It turns out this is not immediately possible. To my knowledge none of the included view resolvers provide a straight mapping between URLs and views so I had to write one that configures like this:

<bean class="UrlToViewViewResolver">
   <map name="urlToViewMap">
      <property key="/user/rss/*" value="rssView"/>
      <property key="/user/activity/*" value="jsonView"/>
      ...
   </map>
</bean>

The implementation (not included) is trivial and based on AntPathMatcher, a utility class readily available in Spring.


JSR 303 bean validation not applied in @Controllers (SPR-6928, scheduled for 3.1)
On Springsource’s weblog there’s a nice tutorial on how to get validation and type conversion in Spring 3.0 here. When it comes to validation though, Spring only supports it “after binding user input to it”. It is not possible to validate arguments and return types for a RESTful controller (no web forms or user input of any kind there). This means this won’t work out of the box:

   @RequestHandler("/admin/", method = RequestMethod.PUT )
   public void updateUser(@Valid User user) {
      ...
      //returns a POJO that should always be resolved by an RSS view
   }

since in a RESTful scenario the User object won’t be bound from a form but unmarshalled from XML/JSON/CSV or any other wire format.
To enable validation for a RESTful controller I chose to use @Valid at the method level and add an around advice that intercepts all methods annotated as such then use a Validator to validate arguments and return objects.

<aop:config proxy-target-class="true">
	<aop:pointcut id="validatableMethodExecution" 
        expression="execution(* com.mycorp..*.*(..)) and @annotation(javax.validation.Valid)"/>
	
	<aop:aspect id="validateArgumentsAndReturnObject" ref="validatorAspect">
		<aop:around pointcut-ref="validatableMethodExecution" method="validate"/>
	</aop:aspect>
</aop:config>

public class ModelValidatorAspect {
	@Autowired
	private Validator validator;
	
    public Object validate(ProceedingJoinPoint pjp) throws Throwable {
        Set<ConstraintViolation> violations =new HashSet<ConstraintViolation>();
        for(Object obj : pjp.getArgs()) {
            if(obj != null) {
                violations.addAll(validator.validate(obj));
            }
        }
        if(!violations.isEmpty()) {
            throw new BadRequestException(composeErrorMessage(violations));
        }

        Object ret = pjp.proceed();

        //validate return object and throw exception if the validation fails. 
        if(ret != null) {
            violations = validator.validate(ret);
        }
        if(!violations.isEmpty()) {    
            throw new BusinessValidationException(violationsStr);
        }
   }
}

This could get more complex if you have deal with collections of validatable objects but it’s not hard to enhance the code to handle that.


Formatting responses (i.e. Date) not working using Spring formatter annotations
Spring comes with some niceties like @DateTimeFormat and @NumberFormat and naturally I want to rely on them by annotating the class attributes of return types used by @Controllers.
That’s not quite working since the formatters work with form-backed model objects. However, if you use a MappingJacksonJsonView there is a way of formatting using Jackson’s own @JsonSerialize.

Although at times it felt that the switch to SpringMVC was too much, the features that come with it (like abstracting web-related properties as annotations) will turn your RESTful controllers into annotated POJOs focused on the business domain which ultimately means maintainable code.

Update Mar.04 I left out several points about JSON marshalling that didn’t work as expected (Jackson marshaller is a handful).
- One enhancement I suggested to ease the pain of configuring JSON marshalling is creating a namespace similar to oxm (SPR-6943)
- The second was to add Jetisson support (SPR-6938) as an alternative to Jackson JSON marshaller.

I’m keen on your feedback regarding these issues so if you have better solutions please comment.

Share and enjoy:
  • del.icio.us
  • Digg
  • Facebook
  • Mixx
  • description
  • Reddit
  • StumbleUpon
  • Technorati
  • TwitThis
Tagged with: , ,
Posted in java, software, spring
  • http://joshdevins.net Josh Devins

    So, overall, would you say that your move was worth while? I’m thinking you should present this transition to the other teams maybe with a pros and cons of switching, why you switched, etc.

  • http://newsplore.com Florin

    Personally I think yes, it was worth it. If nothing else, it suddenly clarified the layer separation in our app and created the opportunity for a drastic refactoring move where DTOs and controllers along with all top-level validation were consolidated into one logical tier, leaving the services tier free from any view-related logic and concerned only with the core domain.

    Arguably a proper REST service implementation using RESTEasy would achieve the same goals I posted above but with our @Controllers being POJOs now, a port to any proper JAX-RS implementation means refactoring only annotations and nothing else. This newly acquired portability freedom makes the choice of any such technology being based on aspects like performance and integration options (i.e. does it support Commons HttpClient or Mina?) rather than implementation details.

  • Anonymous

    These are excellent points that need to be publicised more. Spring guys are hard at work promoting their MVC framework as comparable with JAX-RS even though it is not. Please add a link to this page in the reply section of the InfoQ article that you mention.

  • AndyC

    Re “Same relative paths in multiple @Controllers not supported”. Is this because of the way that the @Controllers were discovered? Have you got a that is picking up both @Controllers i.e. can you configure the base-package (or some other attribute) so that only the right controllers for each servlet are discovered?

    Re “@ExceptionHandler is controller-centric”. I believe that is intentional. You can configure global exception handling behaviour via HandlerExceptionResolvers.

  • Sven Haiges

    Did you have the problem htat an @Exceptionhandler needs to write out an exception message in either json or xml? That is another unsupported feature, e.g. you cannot return a bean from an exception handler and expect it to be automatically serialized to xml or json based on the incoming accept header.

  • Anonymous

    Hmm, I haven’t tried returning a bean from an exception handler (certainly interesting!). Did you file a bug yet?

  • Stankovic Vlada

    Is it possible to have same url mapping in two different controllers?

  • Anonymous

    I don’t think this is possible.

  • phatpenguin

    Just wanted to bump this and let you know 3 years later this information is still valuable. Thanks for taking the time to share your experiences!

blog comments powered by Disqus