The default Spring Data binding mechanism allows us to bind the HTTP request details to the application specific objects.
The object must obey JavaBean convention.
Spring uses WebDataBinder which is a subclass of DataBinder.
WebDataBinder uses PropertyEditors/ConversionService to bind request parameter to handler method arguments. The PropertyEditor concept is part of the JavaBeans specification.
JavaBeans specs and API quick tutorial
Also see Spring Core PropertyEditors support and Conversion Services.
Examples
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
Binding Request Parameters
Using @RequestParam
As we saw in the previous tutorials that one way to bind request parameters is to use @RequestParam annotation. In this example we are mapping /trades?buySell=buy&buyCurrency=EUR&sellCurrency=USD:
@Controller
@RequestMapping("trades")
public class TradeController {
@RequestMapping
public String handleTradeRequest (@RequestParam("buySell") String buySell,
@RequestParam("buyCurrency") String buyCurrency,
@RequestParam("sellCurrency") String sellCurrency,
Model map) {
String msg = String.format(
"trade request. buySell: %s, buyCurrency: %s, sellCurrency: %s",
buySell, buyCurrency, sellCurrency);
map.addAttribute("msg", msg);
return "my-page";
}
}
Binding Java Object
@RequestParam should be used if there are few parameters to bind with. But as number of request parameters increases our controller method becomes unmaintainable. There, we should create a Java class to map the request parameters to the class properties. The class should be standard JavaBean and property name should match the request param names.
Creating Object to capture request params:
public class Trade {
private String buySell;
private String buyCurrency;
private String sellCurrency;
public String getBuySell () {
return buySell;
}
public void setBuySell (String buySell) {
this.buySell = buySell;
}
.................
}
Using above object in the controller:
@Controller
@RequestMapping("trades")
public class TradeController {
@RequestMapping
public String handleTradeRequest (Trade trade,
Model map) {
String msg = String.format(
"trade request. buySell: %s, buyCurrency: %s, sellCurrency: %s",
trade.getBuySell(), trade.getBuyCurrency(),
trade.getSellCurrency());
map.addAttribute("msg", msg);
return "my-page";
}
Note we don't have to use the annotation @RequestParam with binding object.
We can also use our binding object in the JSP view without explicitly adding that into the Model object.
<%@ page language="java"
contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<html>
<body>
<h3> Message : ${msg} <h3>
<p>BuySell: ${trade.buySell}</p>
<p>Buy Currency: ${trade.buyCurrency}</p>
<p>Sell Currency: ${trade.sellCurrency}</p>
</body>
</html>
In fact the Model object is already populated with our binding object before the handler method is called by Spring. You can see what's inside Model by printing it in the handler.
Binding Path Variables
Using @PathVariables
Modifying our above example to use URI template variables:
@Controller
@RequestMapping("trades")
public class TradeController {
@RequestMapping("{buySell}/{buyCurrency}/{sellCurrency}")
public String handleTradeRequest3 (@PathVariable("buySell") String buySell,
@PathVariable("buyCurrency") String buyCurrency,
@PathVariable("sellCurrency") String sellCurrency,
Model map) {
String msg = String.format(
"trade request. buySell: %s, buyCurrency: %s, sellCurrency: %s",
buySell, buyCurrency, sellCurrency);
map.addAttribute("msg", msg);
return "my-page";
}
}
The above handler will map to the request: /trades/buy/EUR/USD
Using Java Object
We don't have to use @PathVariable annotation:
@Controller
@RequestMapping("trades")
public class TradeController {
@RequestMapping("{buySell}/{buyCurrency}/{sellCurrency}")
public String handleTradeRequest (Trade trade, Model map) {
String msg = String.format(
"trade request. buySell: %s, buyCurrency: %s, sellCurrency: %s",
trade.getBuySell(), trade.getBuyCurrency(),
trade.getSellCurrency());
map.addAttribute("msg", msg);
return "my-page";
}
}
Unit Tests
package com.logicbig.example;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@SpringJUnitConfig(classes = MyWebConfig.class)
@WebAppConfiguration
public class TradeControllerTest {
private final MockMvcTester mockMvcTester;
@Autowired
public TradeControllerTest(WebApplicationContext wac) {
this.mockMvcTester = MockMvcTester.from(wac);
}
@Test
void testUserController() {
// 1. Using explicit param() method (Cleaner than manual string building)
assertThat(mockMvcTester.get().uri("/trades/paramTest")
.param("buySell", "buy")
.param("buyCurrency", "EUR")
.param("sellCurrency", "USD"))
.hasStatusOk();
// 2. Query Params via URI (Still supported, but param() is usually preferred)
assertThat(mockMvcTester.get()
.uri("/trades?buySell=buy&"
+ "buyCurrency=EUR&"
+ "sellCurrency=USD"))
.hasStatusOk();
// 3. Path Variables (Simple template)
assertThat(mockMvcTester.get()
.uri("/trades/buy/EUR/USD"))
.hasStatusOk();
// 4. Path Variables using URI template arguments (Best Practice)
assertThat(mockMvcTester.get()
.uri("/trades/pathTest/{action}/{buy}/{sell}",
"buy", "EUR", "USD"))
.hasStatusOk();
}
}
mvn test -Dtest="TradeControllerTest" Output$ mvn test -Dtest="TradeControllerTest" [INFO] Scanning for projects... [INFO] [INFO] ------------< com.logicbig.example:spring-mvc-data-binding >------------ [INFO] Building spring-mvc-data-binding 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ war ]--------------------------------- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ spring-mvc-data-binding --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-mvc-data-binding\src\main\resources [INFO] [INFO] --- compiler:3.15.0:compile (default-compile) @ spring-mvc-data-binding --- [INFO] Nothing to compile - all classes are up to date. [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-mvc-data-binding --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\spring-mvc-data-binding\src\test\resources [INFO] [INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ spring-mvc-data-binding --- [INFO] Nothing to compile - all classes are up to date. [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ spring-mvc-data-binding --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.logicbig.example.TradeControllerTest [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.477 s -- in com.logicbig.example.TradeControllerTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.449 s [INFO] Finished at: 2026-03-18T11:54:07+08:00 [INFO] ------------------------------------------------------------------------ INFO: Completed initialization in 1 ms Mar 18, 2026 11:54:07 AM com.logicbig.example.TradeController handleTradeRequest INFO: trade request. buySell: buy, buyCurrency: EUR, sellCurrency: USD Mar 18, 2026 11:54:07 AM com.logicbig.example.TradeController handleTradeRequest INFO: trade request. buySell: buy, buyCurrency: EUR, sellCurrency: USD Mar 18, 2026 11:54:07 AM com.logicbig.example.TradeController handleTradeRequest2 INFO: trade request. buySell: buy, buyCurrency: EUR, sellCurrency: USD Mar 18, 2026 11:54:07 AM com.logicbig.example.TradeController handleTradeRequest3 INFO: trade request. buySell: buy, buyCurrency: EUR, sellCurrency: USD
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 3.2.3.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- spring-test 7.0.6 (Spring TestContext Framework)
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
- assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
- JDK 25
- Maven 3.9.11
|