How do PropertyEditors and the ConversionService differ for converting request values, and which should you prefer?
answer
- PropertyEditor = old, stateful, per-binding, String↔one type
- ConversionService = Converter/Formatter, stateless, global, any→any
- Editors registered in @InitBinder; converters in addFormatters
- Editor wins over ConversionService when both match
- @DateTimeFormat/@NumberFormat = ConversionService formatters
basics
~10 sPropertyEditors are the old JavaBeans mechanism: stateful, not thread-safe, registered per binding, only String↔Object. ConversionService (Converter/Formatter) is the modern, stateless, thread-safe, type-to-any-type system registered globally. Prefer the ConversionService.
solid answer
~40 sBoth convert string request values into typed properties during WebDataBinder binding, but they are different generations. `PropertyEditor` (from `java.beans`) is stateful and not thread-safe, so it must be registered per-binding — typically inside `@InitBinder` via `registerCustomEditor`, or Spring's built-in editors (`CustomDateEditor`, etc.). It only converts between `String` and one target type. The `ConversionService` (Spring 3+) uses stateless, thread-safe `Converter<S,T>`, `ConverterFactory`, `GenericConverter`, and locale-aware `Formatter` implementations, supports arbitrary source→target pairs, and is registered once globally (usually a `FormattingConversionService`/`WebConversionService` with `@ConfigurationPropertiesBinding` or by adding to `FormatterRegistry` in `WebMvcConfigurer.addFormatters`). Prefer the ConversionService for new code: it's shared, faster to reason about, and works beyond web binding. PropertyEditors remain useful for quick per-controller conversions. When both can handle a type, WebDataBinder consults registered custom editors first, then the ConversionService.
code
java · 23 lines// Modern: global, thread-safe converter
@Component
class StringToMoneyConverter implements Converter<String, Money> {
@Override public Money convert(String source) {
return Money.parse(source); // e.g. "USD 12.50"
}
}
@Configuration
class WebConfig implements WebMvcConfigurer {
@Override public void addFormatters(FormatterRegistry reg) {
reg.addConverter(new StringToMoneyConverter());
}
}
// Legacy: controller-local, stateful editor
@Controller
class ReportController {
@InitBinder
void binder(WebDataBinder b) {
b.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
}go deeper
Know that both convert form/param strings into typed properties.
Contrast stateful per-binding editors with the global ConversionService and know where each is registered.
Explain precedence (editor before ConversionService), Formatter vs Converter, and @DateTimeFormat/@NumberFormat integration.
Set a conversion strategy: standardize on ConversionService/Formatters app-wide, reserve editors for legacy, and keep JSON (Jackson) conversion configured separately on the ObjectMapper.
## Two generations of type conversion When `WebDataBinder` binds `?date=2026-07-22` onto a `LocalDate date` property, it must convert the `String` to a `LocalDate`. Spring has two systems for that. ### PropertyEditor (legacy, java.beans) - Interface: `java.beans.PropertyEditor`; Spring provides `PropertyEditorSupport` and ready-made editors like `CustomDateEditor`, `CustomNumberEditor`, `StringTrimmerEditor`. - **Stateful** (it holds the value being edited) and therefore **not thread-safe** — it must be created and registered *per binding*. - Registered via `WebDataBinder.registerCustomEditor(Type.class, editor)`, normally inside an `@InitBinder` method, or via a `PropertyEditorRegistrar`. - Only bridges `String` ↔ a single target type (`getAsText`/`setAsText`). - Spring ships default editors auto-registered for common types. ```java @InitBinder void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true)); binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); // trim + empty->null } ``` ### ConversionService (modern, Spring 3+) - Central interface `org.springframework.core.convert.ConversionService`; web MVC uses a `FormattingConversionService` (Boot: `WebConversionService`). - Built from **stateless, thread-safe** components: - `Converter<S,T>` — single source→target. - `ConverterFactory<S,R>` — source→a range of targets (e.g. String→Enum). - `GenericConverter` — full control, multiple type pairs, field context. - `Formatter<T>` — locale-aware `parse`/`print`, ideal for web (dates, numbers, currency); registered in a `FormatterRegistry`. - Registered **once, globally**, and reused across all requests and even non-web code. - Supports **any** source→target type, not just from `String`. ```java @Component class StringToLocalDateConverter implements Converter<String, LocalDate> { public LocalDate convert(String s) { return LocalDate.parse(s); } } @Configuration class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToLocalDateConverter()); registry.addFormatter(new org.springframework.format.datetime.standard.DateTimeFormatterRegistrar() .getReadablePrinter()); // illustrative } } ``` Also: annotation-driven formatting via `@DateTimeFormat` and `@NumberFormat` on fields is handled by the ConversionService's formatter support. ### Precedence inside WebDataBinder When converting a value, `WebDataBinder`/`TypeConverterDelegate` first looks for a **custom PropertyEditor** registered for that type/field; if none applies, it falls back to the **ConversionService**. So a per-`@InitBinder` editor can override the global conversion for that controller. ### Which to prefer - **Prefer the ConversionService/Formatters** for new applications: thread-safe singletons, reusable outside web binding, cleaner, richer type support, and integrate with `@DateTimeFormat`/`@NumberFormat`. - **Use PropertyEditors** for small, controller-local, or legacy conversions, or Spring's convenient built-ins like `StringTrimmerEditor`. ### Gotchas - Registering a global `Converter` as a Spring bean does **not** automatically add it to web binding unless it's a `@Component` picked up by Boot's converter registration or added via `addFormatters`; know your registration path. - A stateful editor accidentally shared across threads (e.g. stored as a singleton field) causes corruption — always create fresh in `@InitBinder`. - Neither applies to `@RequestBody`: Jackson does that conversion, so date formats etc. are configured on the `ObjectMapper`, not here.
- If both a custom PropertyEditor and a ConversionService converter can handle String→LocalDate, which does WebDataBinder use?The custom PropertyEditor takes precedence. WebDataBinder's TypeConverterDelegate checks for a registered custom editor for the type/field first and only falls back to the ConversionService when no matching editor is found.
- Why is a Formatter often better than a Converter for web date fields?Formatter is locale-aware — it has parse and print with a Locale — so it handles user-facing input and display formats and integrates with @DateTimeFormat/@NumberFormat, whereas a plain Converter is one-directional and locale-agnostic.
saying these in an interview costs you the question
- Calling PropertyEditors thread-safe or registering one as a shared singleton
- Thinking ConversionService only converts from String
- Believing @DateTimeFormat is handled by PropertyEditors
- Assuming a @Component Converter automatically affects @RequestBody JSON parsing