Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Learn › Spring › Web MVC

Request mapping and parameter binding

How does a value sent in a request become a method parameter?

Request mapping decides which controller method receives a request, based on its URL and HTTP method, and parameter binding puts the values carried by the request into the parameters of that method. An HTTP request can carry a value in several places: the path of the URL, the query string after the ?, and the body. The annotation on a parameter of a controller method decides "which of these places the value is taken from". If the place that carries the value and the place the value is taken from do not match, the value does not come in.

Checked against real execution · Checked by sending requests for 48 combinations to a Spring Boot 4.1.1 server

Concepts in this lab

@RequestParam and @PathVariable
These are annotations that let a controller method receive a single value carried in the request as a method parameter. @RequestParam takes the value from the request parameters (the name=value pairs of the query string and of form data in the body), and @PathVariable takes it from a {name} placeholder declared in the path of the URL, as in /orders/{id}. The value taken is text, so Spring converts it to the type of the parameter (int, for example), and when text that cannot be converted arrives, Spring does not run the controller and responds with 400. By default, @RequestParam gives 400 when the value is missing; with required = false, null is passed in instead, and with defaultValue, that value is passed in instead. When you use required = false, declare the parameter with an object type such as Integer, not int, so that it can hold null.
@ModelAttribute and @RequestBody
These are two ways of receiving several values of a request as one object. @ModelAttribute creates the object first and then uses the setters to set the request parameters (query string and form data in the body) whose names match the fields (for a class that takes its values through a constructor, such as a record, the values are passed as constructor arguments when the object is created). @RequestBody passes the whole request body to the message converter that matches the Content-Type, which converts it into an object, and it is mostly used to receive a JSON body. The two look at different places, so a value sent in a JSON body does not come in through @ModelAttribute, and a value sent in the query string does not come in through @RequestBody. @ModelAttribute leaves a field with no value as it is and does not treat it as an error, so a value that is required has to be validated separately.
Query string and form data
The query string is the part of a URL after the ? in which name=value pairs are joined with &, and form data is text of the same shape sent in the request body. When form data is sent, the Content-Type header is set to application/x-www-form-urlencoded. When an HTML form is submitted with GET, the values are carried in the query string, and when it is submitted with POST, they are carried as form data in the body. The servlet container combines the two and provides them as "request parameters", so server code takes the values in the same way without telling which of the two they came from. A JSON body is not included in the request parameters.
HandlerMethodArgumentResolver
This component takes a value out of the request and builds it so that it can be passed to a parameter of a controller method. Parameters annotated with @RequestParam, @PathVariable, @RequestBody, and so on are filled in through it. If you implement your own, a controller can receive a recurring value, such as the logged-in user's information, directly as a parameter.
HTTP status codes
A three-digit number that shows the result of the server's handling of a request. 2xx means success, 3xx means a move to another location (redirection), 4xx means a problem with the client's request, and 5xx means an error on the server side. Common ones are 200 (success), 201 (created), 400 (bad request), 401 (authentication required), 403 (no permission), 404 (not found), and 500 (server error).

Try these

Running with the default settings

  1. The request arrives: POST /orders. qty is carried in the body as JSON. The Content-Type header tells the format of the body. Wherever a value is carried, everything inside an HTTP request is text.
  2. @PostMapping("/orders"): Spring uses the HTTP method and the path of the request to find the controller method to run (request mapping). The HTTP method is POST and the path is /orders, so the order method is chosen. This step does not look at the query string or the body.
  3. @RequestParam int qty: Next, the values carried by the request are put into the parameters of the method (parameter binding). The annotation on the parameter decides where the value is taken from. @RequestParam looks in the "request parameters" for the value named qty. The request parameters are the query string and the form data in the body combined.
  4. Getting the value: A JSON body is not a request parameter. Even though qty is written inside the body, @RequestParam does not open the body. By default, @RequestParam requires the value to be present, so Spring responds with 400 (MissingServletRequestParameterException). The controller method does not run.

Result of this run

Code

// Request sent
// POST /orders
// Content-Type: application/json
//
// {"qty": 3}

@RestController
class OrderController {

    @PostMapping("/orders")
    String order(@RequestParam int qty) {
        return "Quantity " + qty;
    }
}

Assumptions of this model

All 48 combinations of the 6 ways of receiving the value and the 8 requests were sent as HTTP requests to a real Spring Boot 4.1.1 server (Spring Framework 7.0.9, embedded Tomcat 11), and the status code, whether the controller ran, the value the controller received, and the name of the exception Spring threw were confirmed to match the screen. The names of the exceptions depend on the Spring version (NoResourceFoundException when no URL matches and MethodArgumentNotValidException for a failed @ModelAttribute conversion are as of this version). The body of an error response varies with the version and the configuration, so it was not treated as a result. The lab is simplified to a controller with one method and one parameter, and the mapping annotation shown is whichever of @GetMapping and @PostMapping matches the HTTP method of the request (the 405 for an HTTP method that does not match is in the "Servlets" lab). OrderForm is a class with a default constructor and a setter. For an object that receives its values through a constructor, such as a record, the behavior when the value is missing can be different, and that was not verified. Finding the value by the parameter name without writing the name separately, as in @RequestParam int qty, is the behavior when the code is compiled with the -parameters option, and the check was done that way too. Four cases that are not on the screen were also checked on the same server. When an empty value is sent, as in ?qty=, @RequestParam int gives 400, required = false gives null, and defaultValue gives the default value. When required = false is used with the type int and no value is sent, null cannot be stored and the result is 500. When a BindingResult parameter is placed immediately after the @ModelAttribute parameter, the controller runs even if the conversion fails. When the body is {}, the qty received with @RequestBody is 0. Validation that checks the range of a value (@Valid) is not covered in this lab and is covered separately in the next lab, "Validation".

Previous lab: Spring MVC request flow · Next lab: Validation

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these