skip to content

How do PropertyEditors and the ConversionService differ for converting request values, and which should you prefer?

level: seniorimportance: should knowfreq 48%

answer

  1. PropertyEditor = old, stateful, per-binding, String↔one type
  2. ConversionService = Converter/Formatter, stateless, global, any→any
  3. Editors registered in @InitBinder; converters in addFormatters
  4. Editor wins over ConversionService when both match
  5. @DateTimeFormat/@NumberFormat = ConversionService formatters

basics

~10 s

PropertyEditors 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 s

Both 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
java
// 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

for a junior

Know that both convert form/param strings into typed properties.

for a middle

Contrast stateful per-binding editors with the global ConversionService and know where each is registered.

for a senior

Explain precedence (editor before ConversionService), Formatter vs Converter, and @DateTimeFormat/@NumberFormat integration.

for a principal

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

context