Master Spring Boot Data Binding: Constructor & Property Binding with Custom Editors
This article explains how Spring Boot 3.4.2 handles data binding using constructors and property setters, demonstrates practical examples with code, shows how to resolve binding errors with BindingResult, and guides you through creating and registering custom PropertyEditors for complex type conversion.
1. Introduction
Data binding maps user input (a map of property paths to values) to target objects following JavaBeans conventions. The main class DataBinder supports two binding modes:
Constructor binding : binds input to a public constructor, matching constructor parameters.
Property binding : binds input to setter methods, matching keys to object properties.
Both modes can be used together or separately.
2. Practical Examples
2.1 Constructor Binding
Three steps are required:
Create a
DataBinderwith
nullas the target object.
Set
targetTypeto the desired class.
Call
constructwith a
ValueResolverthat supplies the input values.
<code>public class User {
private Long id;
private String name;
private Integer age;
// getters and setters
}
Map<String, Object> values = Map.of(
"id", "666",
"age", "33",
"f_name", "Pack"
);
DataBinder binder = new DataBinder(null);
binder.setTargetType(ResolvableType.forClass(User.class));
ValueResolver valueResolver = new ValueResolver() {
@Override
public Object resolveValue(String name, Class<?> type) {
return values.get(name);
}
@Override
public Set<String> getNames() {
return Set.of("id", "name", "age", "email", "address");
}
};
binder.construct(valueResolver);
Object target = binder.getTarget();
System.err.println(target);
</code>The initial run shows no data bound because
Useronly has a default constructor. Adding a matching constructor fixes the binding:
<code>public User(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
</code>If multiple constructors exist, Spring prefers the no‑arg constructor; if none exists, it selects a suitable argument constructor.
2.2 Property Binding
The BeanWrapper API allows setting and retrieving properties, including nested and indexed properties. Example:
<code>public class Department {
private String code;
private String deptName;
private Employee employee;
}
public class Employee {
private String name;
private Integer age;
}
</code> <code>Department target = new Department();
BeanWrapper depart = new BeanWrapperImpl(target);
depart.setPropertyValue("code", "D-0001");
PropertyValue pv = new PropertyValue("deptName", "研发部");
depart.setPropertyValue(pv);
BeanWrapper emp = new BeanWrapperImpl(new Employee());
emp.setPropertyValue("name", "Pack");
emp.setPropertyValue("age", "23");
depart.setPropertyValue("employee", emp.getWrappedInstance());
System.err.println(depart.getWrappedInstance());
</code>Binding request parameters to an object can be done with
ServletRequestParameterPropertyValues:
<code>@GetMapping("/save")
public ResponseEntity<?> save(HttpServletRequest request) {
BeanWrapper bw = new BeanWrapperImpl(Address.class);
PropertyValues pvs = new ServletRequestParameterPropertyValues(request);
bw.setPropertyValues(pvs);
return ResponseEntity.ok(bw.getWrappedInstance());
}
</code>2.3 Type Conversion
Spring uses PropertyEditor implementations to convert between strings and target types (e.g., dates). Custom editors can be registered to handle complex types such as
Address:
<code>public class AddressEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null || "".equals(text)) {
setValue(null);
} else {
String[] parts = text.split(",");
setValue(new Address(parts[0], parts[1]));
}
}
}
</code>Spring automatically discovers an editor named
AddressEditorin the same package as
Address. To register it programmatically, use CustomEditorConfigurer :
<code>@Bean
CustomEditorConfigurer customEditorConfigurer() {
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setCustomEditors(Map.of(Address.class, AddressEditor.class));
return configurer;
}
</code>Alternatively, implement PropertyEditorRegistrar for reusable registration:
<code>@Component
public final class AddressPropertyEditorRegistrar implements PropertyEditorRegistrar {
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Address.class, new AddressEditor());
}
}
</code>Use the registrar in a controller via
@InitBinder:
<code>@RestController
@RequestMapping("/persons")
public class PersonController {
private final AddressPropertyEditorRegistrar registrar;
public PersonController(AddressPropertyEditorRegistrar registrar) {
this.registrar = registrar;
}
@InitBinder
public void init(WebDataBinder binder) {
registrar.registerCustomEditors(binder);
}
@GetMapping("/create")
public ResponseEntity<?> create(Person person) {
return ResponseEntity.ok(person);
}
}
</code>These techniques enable robust data binding, error handling with
BindingResult, and custom type conversion in Spring Boot applications.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.