What is @InitBinder?
To customize request parameter data binding, we can use @InitBinder annotated methods within our controller.
This customization is not limited to request parameters, it can be applied to template URI variables and backing/command objects.
The signature of @InitBinder method.
The methods annotated with @InitBinder support all arguments types that handler methods supports, except for command/form objects and corresponding validation result objects.
One of the argument should be WebDataBinder.
Return type should be void.
@Controller
public class MyController{
@InitBinder
public void customizeBinding (WebDataBinder binder, ......) {
}
....
}
What we can do with WebDataBinder?
WebDataBinder extends DataBinder.
It can be used to register custom formatter, validators and PropertyEditors.
WebDataBinder.addCustomFormatter(..);
WebDataBinder.addValidators(..);
WebDataBinder.registerCustomEditor(..);
When @InitBinder methods get called?
The @InitBinder annotated methods will get called on each HTTP request if we don't specify the 'value' element of this annotation.
Each time this method is called a new instance of WebDataBinder is passed to it.
To be more specific about which objects our InitBinder method applies to, we can supply 'value' element of the annotation @InitBinder. The 'value' element is a single or multiple names of command/form attributes and/or request parameters that this init-binder method is supposed to apply to.
@InitBinder("user")
public void customizeBinding (WebDataBinder binder) {...}
We can define multiple @InitBinder methods having different names.
Example
We are going to enhance the previous example of User registration. we will demonstrate how to use custom PropertyEditor to setup an implicit conversion of input String to Date.
Also see Spring Core PropertyEditors support.
Create Backing/Command Object
Comparing with our last example we are going to add an additional dateOfBirth field.
public class User {
private Long id;
@Size(min = 5, max = 20)
private String name;
@Size(min = 6, max = 15)
@Pattern(regexp = "\\S+", message = "Spaces are not allowed")
private String password;
@NotEmpty
@Email
private String emailAddress;
@NotNull
private Date dateOfBirth;
//getters and setters
}
Create Controller with @InitBinder annotated method
@Controller
@RequestMapping("/register")
public class UserRegistrationController {
@Autowired
private UserService userService;
@InitBinder("user")
public void customizeBinding (WebDataBinder binder) {
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd");
dateFormatter.setLenient(false);
binder.registerCustomEditor(Date.class, "dateOfBirth",
new CustomDateEditor(dateFormatter, true));
}
@RequestMapping(method = RequestMethod.GET)
public String handleGetRequest (Model model) {
model.addAttribute("user", new User());
return "user-registration";
}
@RequestMapping(method = RequestMethod.POST)
public String handlePostRequest (@Valid @ModelAttribute("user") User user,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "user-registration";
}
userService.saveUser(user);
model.addAttribute("users", userService.getAllUsers());
return "registration-done";
}
}
Running Example
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
In case of validation errors:
Successful form submission:
Post request via Curl:
$ curl -s http://localhost:8080/spring-custom-property-editor/register -d "name=Joe&emailAddress=joe@example.com&dateOfBirth=1976-10-10&password=abc"
<html> <head> <style> span.error { color: red; } </style> </head> <body>
<h3> Registration Form <h3> <br/> <form id="user" action="register" method="post"> <pre> Name <input id="name" name="name" type="text" value="Joe"/> <span id="name.errors" class="error">size must be between 5 and 20</span>
Date of Birth <input id="dateOfBirth" name="dateOfBirth" type="date" value="1976-10-10"/>
Email address <input id="emailAddress" name="emailAddress" type="text" value="joe@example.com"/>
Password <input id="password" name="password" type="password" value=""/> <span id="password.errors" class="error">size must be between 6 and 15</span> <input type="submit" value="Submit" /> </pre> </form> </body> </html>
Integration Test
package com.logicbig.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@SpringJUnitWebConfig(MyWebConfig.class)
public class RegistrationControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvcTester mockMvc;
@BeforeEach
public void setup() {
mockMvc = MockMvcTester.from(wac);
}
@Test
public void testPostValidUser_shouldSaveAndShowDoneView() throws Exception {
assertThat(
mockMvc
.post()
.uri("/register")
.param("name", "KatieK")
.param("emailAddress", "kat@example.com")
.param("password", "abcadf")
.param("dateOfBirth", "1976-10-10")
.exchange())
.hasStatusOk()
.hasViewName("registration-done")
.model()
.containsKey("users")
.extractingByKey("users")
.asList()
.hasSize(1);
}
@Test
public void testPostUserWithValidationErrors_shouldReturnFormViewWithErrors() throws Exception {
assertThat(mockMvc.post()
.uri("/register")
.param("name", "")
.param("emailAddress", "invalid-email")
.param("password", "short")
.param("dateOfBirth", "invalid-date")
.exchange())
.hasStatusOk()
.hasViewName("user-registration")
.model()
.hasAttributeErrors("user")
.extractingBindingResult("user")
.hasErrorsCount(4)
.hasFieldErrors("name",
"emailAddress",
"password",
"dateOfBirth");
}
@Test
public void testPostUserWithInvalidDateFormat_BindError() throws Exception {
assertThat(
mockMvc
.post()
.uri("/register")
.param("name", "John Doe")
.param("emailAddress", "john@example.com")
.param("password", "password123")
.param("dateOfBirth", "10/10/1976")
.exchange())
.hasStatusOk()
.hasViewName("user-registration")
.model()
.hasAttributeErrors("user")
.extractingBindingResult("user")
.hasFieldErrors("dateOfBirth");
}
}
mvn clean test -Dtest="RegistrationControllerTest" Output$ mvn clean test -Dtest="RegistrationControllerTest" [INFO] Scanning for projects... [WARNING] [WARNING] Some problems were encountered while building the effective model for com.logicbig.example:spring-custom-property-editor:war:1.0-SNAPSHOT [WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-war-plugin is missing. @ line 55, column 21 [WARNING] [WARNING] It is highly recommended to fix these problems because they threaten the stability of your build. [WARNING] [WARNING] For this reason, future Maven versions might no longer support building such malformed projects. [WARNING] [INFO] [INFO] ---------< com.logicbig.example:spring-custom-property-editor >--------- [INFO] Building spring-custom-property-editor 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ war ]--------------------------------- [INFO] [INFO] --- clean:3.2.0:clean (default-clean) @ spring-custom-property-editor --- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ spring-custom-property-editor --- [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-custom-property-editor\src\main\resources [INFO] [INFO] --- compiler:3.5.1:compile (default-compile) @ spring-custom-property-editor --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 6 source files to D:\example-projects\spring-mvc\spring-custom-property-editor\target\classes [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ spring-custom-property-editor --- [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-custom-property-editor\src\test\resources [INFO] [INFO] --- compiler:3.5.1:testCompile (default-testCompile) @ spring-custom-property-editor --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to D:\example-projects\spring-mvc\spring-custom-property-editor\target\test-classes [INFO] [INFO] --- surefire:3.2.5:test (default-test) @ spring-custom-property-editor --- [INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider [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.RegistrationControllerTest [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.675 s -- in com.logicbig.example.RegistrationControllerTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 11.288 s [INFO] Finished at: 2026-04-26T18:39:42+08:00 [INFO] ------------------------------------------------------------------------ Apr 26, 2026 6:39:41 PM org.hibernate.validator.internal.util.Version <clinit> INFO: HV000001: Hibernate Validator 5.2.4.Final
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 3.2.9.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)
- hibernate-validator 9.0.1.Final (Hibernate's Jakarta Validation reference implementation)
- junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
- jakarta.el 4.0.0 (Jakarta Expression Language Implementation)
- hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
- assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
- JDK 25
- Maven 3.9.11
|