In this article, we are going to see several examples on how to get request parameters with Spring MVC, how to bind them to different objects, how to use @RequestParam annotation and when the annotation is not needed.

@RequestParam examples

@RequestParam is an annotation which indicates that a method parameter should be bound to a web request parameter.

1. Biding method parameter with web request parameter

Let’s say we have our /books URL that requires a mandatory parameter named category:

/books?category=java

we just need to add @RequestParam("parameterName") annotation before the method parameter that we will use for biding the web request parameter.

Let’s see an example:

@RequestMapping("/books")
public String books(@RequestParam("category") String cat, Model model){
    //business logic...
    model.addAttribute("category", cat);
    return "books.jsp";
}

In the snippet above we’ve captured value of request parameter “category” into String cat method argument and we put its value into a Model object so that we can easily test it.

@Test
public void getCategoryParam() throws Exception {
    this.mockMvc.perform(get("/books").param("category", "java"))
            .andExpect(status().isOk())
            .andExpect(model().attribute("category", is("java")))
            .andDo(print());
}

In the test above we are using Spring MVC test library to perform an HTTP GET request to /books with a parameter - category - which has value “java”. Then we verify that the response status is OK (code: 200) and that the model contains an attribute named category and that its value is “java”.

For this test we’ve quickly setup a Spring Boot project, you can find the pom.xml at the end of this article.

Please note that category _is considered a mandatory parameter if we don’t pass _category parameter in our call as in the test below we receive a 400: BadRequestException.

@Test
public void whenNoParam_thenBadRequest() throws Exception {
    this.mockMvc.perform(get("/books"))
            .andExpect(status().isBadRequest());
}

2. Request parameter match method parameter name

If both variable and request parameter name matches we don’t need to specify the parameter name in the @RequestParam annotation.

@RequestMapping("/books")
public String books(@RequestParam String category, Model model){
    model.addAttribute("category", category);
    return "books.jsp";
}

In the example above, since** **both variable and request parameter name is ‘category’, binding is performed automatically.

3. Auto type conversion

If the request parameter is not a String but - for example - a number we can bind it to the corresponding type. Let’s say we have a call like this:

/books?rate=5&maxprice=150.00

In order to bind rate and maxprice respectively with an int and a BigDecimal, we just need to specify the target type after the annotation.

@RequestMapping("/books")
public String books(@RequestParam("rate") int rate,
                         @RequestParam("maxprice") BigDecimal maxprice, Model model){
    model.addAttribute("rate", rate);
    model.addAttribute("maxprice", maxprice);
    return "books.jsp";
}
@Test
public void whenNumber_thenAutoType() throws Exception {
    this.mockMvc.perform(get("/books")
            .param("rate", "5")
            .param("maxprice", "150.00")
    )
            .andExpect(status().isOk())
            .andExpect(model().attribute("rate", is(5)))
            .andExpect(model().attribute("maxprice", is(new BigDecimal("150.00"))));
}

Similarly, if we want to pass a date like:

/books?from=2011-01-01

We need to bind it with respective object:

@RequestMapping("/books")
public String books(@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
                    @RequestParam("from") LocalDate from, Model model){
    model.addAttribute("from", from);
    return "books.jsp";
}
@Test
public void whenDate_thenAutoType() throws Exception {
    this.mockMvc.perform(get("/books")
            .param("from", "2011-01-01")
    )
            .andExpect(status().isOk())
            .andExpect(model().attribute("from", is(LocalDate.of(2011,01,01))));
}

Since dates have different formats, it’s necessary to specify which one should be used in order to parse the value parameter correctly. In our example, we’ve used @DateTimeFormat to specify iso DateTimeFormat.ISO.DATE that is the most common ISO: yyyy-MM-dd.

All simple types such as int,long, Date, etc. are supported.

The conversion process can be configured and customized according to your needs using WebBinder or by registering _Formatters. _

4. @RequestParam with not mandatory parameters

When we add@RequestParam annotation, as we’ve seen, by default we are assuming that request parameter is mandatory. In case we want to specify that is not, we can just add required=false.

@RequestMapping("/books")
public String books(@RequestParam(name="category", required = false) String category, 
    Model model){
    model.addAttribute("category", category);
    return "books.jsp";
}

In this case, if we call just /books _without parameter, we won’t receive a 400 as before. String category in this case will remain _null.

@Test
public void whenParameterNotSent_thenStatusOk() throws Exception {
    this.mockMvc.perform(get("/books"))
            .andExpect(status().isOk())
            .andExpect(model().attribute("category", is(nullValue())));
}

**5. @RequestParam with Default value **

It’s also possible to specify a default value that will be applied if the parameter is not sent. Let’s say our default value is “fantasy” we can add defaultValue = "fantasy" in the annotation as follows:

@RequestMapping("/books")
public String books(@RequestParam(name="category", defaultValue = "fantasy") String category, 
                         Model model){
    model.addAttribute("category", category);
    return "books.jsp";
}

Let’s then perform our test in which we don’t pass category request parameter, expected value it’s gonna be “fantasy”.

public void whenParameterNotSent_thenDefault() throws Exception {
    this.mockMvc.perform(get("/books"))
            .andExpect(status().isOk())
            .andExpect(model().attribute("category", is("fantasy")));
}

@Test

When request parameter is sent, the value should be, of course, the one sent and not our default.

@Test
public void whenParameterSent_thenParameter() throws Exception {
    this.mockMvc.perform(get("/books")
            .param("category", "java")
    )
            .andExpect(status().isOk())
            .andExpect(model().attribute("category", is("java")));
}

6. @RequestParam with List or array

We can also perform a call in which we specify several values for a parameter, like:

/books?authors=martin&authors=tolkien

In this case, we can bind it using a List or an array_ - _this is with a List:

@RequestMapping("/books")
public String books(@RequestParam List<String> authors,
                         Model model){
    model.addAttribute("authors", authors);
    return "books.jsp";
}
@Test
public void whenMultipleParameters_thenList() throws Exception {
    this.mockMvc.perform(get("/books")
            .param("authors", "martin")
            .param("authors", "tolkien")
    )
            .andExpect(status().isOk())
            .andExpect(model().attribute("authors", contains("martin","tolkien")));
}

and with an array:

@RequestMapping("/books")
public String books(@RequestParam String[] authors,
                         Model model){
    model.addAttribute("authors", authors);
    return "books.jsp";
}
@Test
public void whenMultipleParameters_thenArray() throws Exception {
    this.mockMvc.perform(get("/books")
            .param("authors", "martin")
            .param("authors", "tolkien")
    )
            .andExpect(status().isOk())
            .andExpect(model().attribute("authors", arrayContaining("martin","tolkien")));
}

7. @RequestParam with Map

It’s also possible to bind all request parameters in a Map just by adding a Map object after the annotation:

@RequestMapping("/books")
public String books(@RequestParam Map<String, String> requestParams,
                         Model model){
    model.addAttribute("category", requestParams.get("category"));
    model.addAttribute("author", requestParams.get("author"));
    return "books.jsp";
}

In this example we’ve added a Map<String,String>, the key is the request parameter name, so we just get the parameters from the Map using their name. I think it’s worth highlight that with this approach you can’t specify which parameters are mandatory and which not and that the code can be insidious to read since might need to read it all in order to figure out which are the possible parameters used by this method.

In order to validate the snippet let’s perform a little test:

/books?category=fantasy&author=Tolkien

@Test
public void getParameter() throws Exception {
    this.mockMvc.perform(get("/books")
                .param("category", "fantasy")
                .param("author", "Tolkien"))
            .andExpect(status().isOk())
            .andExpect(model().attribute("category", is("fantasy")))
            .andExpect(model().attribute("author", is("Tolkien")));
}

Note that if you have multiple values for the same parameter name, you should use MultiValueMap. In the example below, we use@RequestParam with MultiValueMap<String,List<String>> so that we can store multiple values for the same parameter in a List.

@RequestMapping("/books")
public String books(@RequestParam MultiValueMap<String, List<String>> requestParams,
                         Model model){
    model.addAttribute("category", requestParams.get("category"));
    model.addAttribute("authors", requestParams.get("authors"));
    return "books.jsp";
}

Let’s test it passing two authors and one category:

/books?authors=martin&authors=tolkien&category=top250

@Test
public void whenMultiValues_thenRetrieveFromList() throws Exception {
    this.mockMvc.perform(get("/books")
            .param("authors", "martin")
            .param("authors", "tolkien")
            .param("category", "top250")
    )
            .andExpect(status().isOk())
            .andExpect(model().attribute("category", contains("top250")))
            .andExpect(model().attribute("authors", contains("martin","tolkien")));
}

Examples without @RequestParam

Based on the list of HandlerMethodArgumentResolver configured in your application, @RequestParam can also be omitted. If you have a look at the code of method getDefaultArgumentResolvers() of RequestMappingHandlerAdapter there is the following piece of code at the end:

// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));

Basically, it’s added to the resolvers a RequestParamMethodArgumentResolver with useDefaultResolution set to true. Looking at the documentation we can see that this means that method argument that is a simple type, as defined in _BeanUtils.isSimpleProperty(java.lang.Class<?>), is treated as a request parameter even if it isn’t annotated. The request parameter name is derived from the method parameter name.

Let’s see in the next example what it means.

8. Binding without annotation

We define our controller without adding the usual @RequestParam annotation before the method argument String category

@RequestMapping("/books")
public String helloWorld(String category, Model model){
    model.addAttribute("category", category);
    return "books.jsp";
}

If we run our usual test we will see that it pass! This happens because both variable and request parameter name is category and in the list of default handlers, we have RequestParamMethodArgumentResolver with useDefaultResolution set to true.

Despite it might look convenient, I prefer to always specify the annotation since it clearly states that the argument represents a request parameter.

9. Bind with a field of an object

In a similar way, if we pass as argument a simple pojo object that has a field - with getter and setter - named like the request parameter, the value of the request parameter will be stored in this field.

Let’s define our pojo class:

public class SearchFilterForm{
    private String category;

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }
}

then our Controller:

@RequestMapping("/books")
public String helloWorld(SearchFilterForm myForm, Model model){
    model.addAttribute("category", myForm.getCategory());
    return "books.jsp";
}

and we run our usual test, we will see that also in this case value of request parameter is correctly stored into myForm.getCategory().

pom.xml

For these tests we’ve quickly setup a Spring Boot project, you can find below the pom.xml.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>net.reversecoding</groupId>
	<artifactId>spring-mvc</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>spring-mvc</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>