Search code examples
javascriptjavajsonspring-mvcspring-restcontroller

How can I get a Spring @RestController to accept parameters in JSON format rather than www-form-urlencoded?


Using Spring 4.1.7 on JDK 1.8, I have an @RestController class that looks like this:

@RestController
public class ServiceAController {

    public static final Logger LOG = Logger.getLogger(ServiceAController.class);

    @RequestMapping(value="/rest/servicea", method=RequestMethod.POST)
    public ServiceAResponse serviceA(@RequestParam(value="parmA", defaultValue="defaultParmA") String parmA,
            @RequestParam(value="parmB", defaultValue="defaultParmB") String parmB,
            @RequestParam(value="parmC", defaultValue="defaulParmC") String parmC) {
        LOG.info("Inside Service A handler: " + parmA + " B: "+ parmB + " C: "+ parmC);
}

When I send a POST to /rest/servicea from a javascript like this, everything works, and I see the values "a", "b", and "c" printed in my log:

    var data = {        
        "parmA": "a",
        "parmB": "b",
        "parmC": "c"
    }

    $.ajax({
        type: "POST",
        url: "./rest/servicea",
        contentType: "application/x-www-form-urlencoded",
        data: data,
        dataType: "json",
        success: submitAuthSuccess,
        error: submitAuthFailure 
    })

However, when I try to change the call from the javascript to this (to change the protocol to REST rather than www-urlencode), I get the default values (defaultParmA, defaultParmB, defaultParmC) in my log:

    var data = {        
        "parmA": "a",
        "parmB": "b",
        "parmC": "c"
    }

    $.ajax({
        type: "POST",
        url: "./rest/servicea",
        contentType: "application/json",
        data: JSON.stringify(data),
        dataType: "json",
        success: submitAuthSuccess,
        error: submitAuthFailure 
    })

I think I'm missing something in the @RestController class to get it to parse the JSON rather than expecting www-urlencoded data.

I tried changing the @RequestMapping annotation on the serviceA method to add the consumes="application/json" attribute, but that had no effect.

What can I change to make this work, using JSON rather than urlencoded data in the POST body?


Solution

  • The @RequestParam javadoc states

    Annotation which indicates that a method parameter should be bound to a web request parameter.

    This is retrieved through the various ServletRequest methods for request parameters. For example, ServletRequest#getParameterMap() which states

    Request parameters are extra information sent with the request. For HTTP servlets, parameters are contained in the query string or posted form data.

    In your second snippet, you aren't sending either. You're sending JSON in the request body.

    Spring has a mechanism (it has many, and custom ones) for deserializing that into the data you expect. The standard solution is @RequestBody, assuming you have an appropriately registered HttpMessageConverter that can handle the JSON. Spring automatically registers MappingJackson2HttpMessageConverter if you have Jackson 2 on the classpath, which can correctly deserialize JSON into Java POJO types.

    The documentation gives a number of examples and explains how you would use it. With JSON, you could define a POJO type with fields that correspond to the ones you send

    class RequestPojo {
        private String paramA;
        private String paramB;
        private String paramC;
        // and corresponding getters and setters
    }
    

    and add a @RequestBody annotated parameter of this type to your handler method

    public ServiceAResponse serviceA(@RequestBody RequestPojo pojo) {
        pojo.getParamB(); // do something
        return ...;
    }
    

    Jackson lets you define, through annotations or through configuration of the ObjectMapper, how to deal with absent values.

    @RestController is not involved here. As its javadoc states,

    Types that carry this annotation are treated as controllers where @RequestMapping methods assume @ResponseBody semantics by default.