urig
urig

Reputation: 16831

Spring Boot: Can I post multiple objects as multiple @RequestParams to a REST service?

Using Spring Boot I've build a toy REST service with a single resource like so:

@RestController
@RequestMapping("/checkout")
public class CheckoutRestController {

    @PostMapping("/book")
    public boolean buyBook(@RequestParam CreditCard creditCard, @RequestParam ShippingAddress shippingAddress) {
        return true;
    }
}

Both CreditCard and ShippingAddress are POJOs (plain old Java objects) that I've coded up).

I've tried posting to this endpoint with this payload:

{
    "creditCard" : {
        "nameOnCard":"foo",
        "number":12345678,
        "expiryMonth":12,
        "expiryYear":2018,
        "ccv":100
    },
    "shippingAddress" : {
        "steert":"foor",
        "houseNumber":"1a",
        "city":"bar",
        "state":"bazz",
        "country":"buzz",
        "zipCode":"booz"
    },
}

But I get an error:

{
    "timestamp": "2018-03-13T11:36:52.745+0000",
    "status": 400,
    "error": "Bad Request",
    "message": "Required CreditCard parameter 'creditCard' is not present",
    "path": "/checkout/book"
}

I know a workaround would be to wrap both POJOs in a request wrapper but I'd rather not unless I really have to.

Is it possible to post two @RequestParam annotated objects? If so, what would the JSON look like?

Upvotes: 5

Views: 21359

Answers (2)

filpa
filpa

Reputation: 3634

@RequestParam parameters are query parameters, not body parameters.

This means that your method:

@PostMapping("/book")
public boolean buyBook(@RequestParam CreditCard creditCard, @RequestParam 
    ShippingAddress shippingAddress) {

        return true;
}

Expects the following request:

POST /checkout/book?creditCard=<...>&shippingAddress=<...>

However, Spring doesn't know how to convert a String query parameter to CreditCard or to ShippingAddress.

You might solve this by implementing a Converter, as follows:

public class StringToCreditCardConverter implements Converter<String, CreditCard> {

    @Override
    public CreditCard convert(String source) {
         <...>
    }
}

However, I do not recommend this as it is not the standard for request bodies and would cause quite a lot of confusion and maintainability issues.

Instead, the recommended way is as follows:

@PostMapping("/book")
public boolean buyBook(@RequestBody BookCreationRequest bookCreationRequest) {
        CreditCard creditCard = bookCreationRequest.getCreditCard();
        ShippingAddress shippingAddress = bookCreationRequest.getShippingAddress();
        ...
}

With the bookCreationRequest containing the CreditCard and ShippingAddress fields (note: you could use e.g. lombok to reduce the boilerplate code):

public class BookCreationRequest {
    private ShippingAddress shippingAddress;
    private CreditCredit creditCard;

    public ShippingAddress  getShippingAddress() {...}
    public CreditCard       getCreditCard() {...}

    public BookCreationRequest(ShippingAddress shippingAddress, CreditCard creditCard) {
        this.creditCard = creditCard;
        this.shippingAddress = shippingAddress;
}

Which would then expect a request JSON as follows:

POST /checkout/book
Payload:
{
    "creditCard": {
        ... 
    },
    "shippingAddress": {
        ...
    }
}

Note that there can only be a single @RequestBody parameter in a request.

Upvotes: 19

hovanessyan
hovanessyan

Reputation: 31443

Yes - create a wrapper Request object. It's not a workaround - it's actually the recommended approach.

You see in your json you have:

{ //wrapper
 {}, //first complex data object / mapped to pojo
 {} //second complex data object / mapped to pojo
}

1) You can easily apply validation to your Request object with @Valid and hence validate both POJOs.

2) You don't have to worry about the order in the payload.

3) You're using @RequestParam incorrectly. @RequestBody is what maps the whole POST payload. You have to remove the @RequestParam annotation and use @RequestBody instead.

Upvotes: 5

Related Questions