skip to content

Explain the legacy PropertyEditor mechanism, why it was superseded by the ConversionService/Formatter SPI, and how the two coexist.

level: seniorimportance: should knowfreq 30%

answer

  1. PropertyEditor = JavaBeans, setAsText/getAsText + setValue/getValue
  2. stateful → NOT thread-safe → one per binding
  3. String-only, no TypeDescriptor/annotations/locale
  4. Spring 3 core.convert replaced it (stateless, shareable)
  5. coexist: ConversionService first, editors as fallback; @InitBinder to register

basics

~20 s

PropertyEditor is the old JavaBeans way to convert String↔Object via setAsText/getAsText. It's stateful, not thread-safe, and String-only, so one instance is needed per binding. Spring introduced the stateless, thread-safe ConversionService/Formatter SPI to replace it, but still supports registered editors for backward compatibility.

solid answer

~40 s

java.beans.PropertyEditor is the original JavaBeans SPI Spring used before Spring 3. An editor converts between a String and an object via setAsText(String)/getAsText() and holds the value with setValue/getValue — meaning it is stateful and NOT thread-safe, so Spring must create/register a fresh instance per binding (e.g. per WebDataBinder). Spring ships editors like CustomDateEditor, CustomNumberEditor, and auto-registers many via PropertyEditorRegistrySupport. Its limits — String-only, stateful, no generics/annotation context, no locale model — drove the Spring 3 core.convert SPI: ConversionService + Converter/Formatter, which are stateless, thread-safe, shareable, and type-to-type. They coexist: a BeanWrapper/DataBinder uses the ConversionService when one is set, otherwise falls back to PropertyEditors; you register editors via @InitBinder or PropertyEditorRegistrar. New code should prefer Converters/Formatters.

code

java · 15 lines
java
// Legacy: a PropertyEditor registered per-request via @InitBinder
@ControllerAdvice
class DateBinding {
    @InitBinder
    void initBinder(WebDataBinder binder) {
        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd"); // fresh per request
        binder.registerCustomEditor(Date.class, new CustomDateEditor(fmt, true));
    }
}

// Modern equivalent: a stateless, thread-safe Converter (preferred for new code)
@Component
class StringToInstantConverter implements Converter<String, Instant> {
    @Override public Instant convert(String source) { return Instant.parse(source); }
}

go deeper

for a junior

Know PropertyEditor is the old String↔Object mechanism and Converters are the modern replacement.

for a middle

Explain setAsText/getAsText, statefulness, and @InitBinder registration.

for a senior

Articulate the exact limitations that motivated core.convert and how the two coexist with precedence.

for a principal

Advise migration strategy, precedence in TypeConverterDelegate, and thread-safety/scoping implications across the container.

## What a PropertyEditor is `java.beans.PropertyEditor` predates Spring — it's part of the **JavaBeans** spec, originally for GUI builders. Spring adopted it as its first type-conversion mechanism. The relevant methods: ```java void setAsText(String text); // String -> object (parse) String getAsText(); // object -> String (print) void setValue(Object value); // holds the current value Object getValue(); ``` Most custom editors extend `java.beans.PropertyEditorSupport` and override `setAsText`/`getAsText`. Spring provides ready-made ones such as `CustomDateEditor`, `CustomNumberEditor`, `CustomCollectionEditor`, `StringTrimmerEditor`, plus many auto-registered defaults in `PropertyEditorRegistrySupport` (used by `BeanWrapperImpl`). ## Why it was superseded PropertyEditors have structural problems: 1. **Stateful** — the value lives inside the editor (`setValue`/`getValue`), so a single instance can't be shared across threads. Spring must instantiate/register editors **per binding** (per `BeanWrapper`/`WebDataBinder`), which is wasteful and error-prone. 2. **String-centric** — they only convert to/from `String`, not arbitrary type→type. 3. **No rich context** — no `TypeDescriptor`, so no access to generics or field annotations. 4. **No first-class locale model** — awkward for i18n formatting. Spring 3 introduced the **`core.convert`** SPI to fix all of these: - `ConversionService` + `Converter`/`ConverterFactory`/`GenericConverter` — **stateless, thread-safe, shareable**, any-type-to-any-type, with `TypeDescriptor` context. - `Formatter`/`AnnotationFormatterFactory` — locale-aware String↔Object, annotation-driven. ## How they coexist today Spring did **not** rip out PropertyEditors — both mechanisms remain and interoperate: - A `BeanWrapperImpl`/`DataBinder`/`SimpleTypeConverter` uses the configured **`ConversionService` first**; if none is set (or it can't convert a pair), it **falls back to registered PropertyEditors**. - You register editors imperatively: in web controllers via `@InitBinder` methods calling `WebDataBinder#registerCustomEditor(...)`, or globally via a `PropertyEditorRegistrar` wired through `CustomEditorConfigurer`. - You register converters/formatters via `ConverterRegistry`/`FormatterRegistry` (MVC's `addFormatters`, or Boot beans). ## When you still meet PropertyEditors - Legacy codebases and some framework internals. - The default String-trimming / built-in coercions in `BeanWrapperImpl` when no ConversionService is configured. ## Gotchas - **Thread-safety**: never register a single editor instance as a shared singleton across threads — its held value makes that unsafe. `@InitBinder` gives you a per-request `WebDataBinder`, which is the correct scope. - **Precedence confusion**: if both a Converter and a PropertyEditor exist for the same pair, the ConversionService (when present) generally takes precedence in `TypeConverterDelegate`; a stray old editor can otherwise mask your new Converter. - Prefer new SPI: for greenfield code, write a `Converter`/`Formatter`, not a `PropertyEditor`.

  • Why can't a PropertyEditor be a shared singleton the way a Converter can?
    Because it stores the converted value internally via setValue/getValue between setAsText and getValue calls; that mutable state makes concurrent use unsafe. A Converter/Formatter holds no per-conversion state, so one instance serves all threads.
  • If both a Converter and a PropertyEditor exist for String→Foo, which wins during binding?
    When a ConversionService is configured, TypeConverterDelegate consults it first, so the Converter generally wins; PropertyEditors act as a fallback when no suitable converter is found or no ConversionService is set.

saying these in an interview costs you the question

  • Claiming PropertyEditors are thread-safe and can be shared singletons
  • Saying ConversionService fully removed/replaced PropertyEditors (they coexist)
  • Thinking PropertyEditors can convert arbitrary type→type (they're String-only)
  • Registering a stateful editor as an application-scoped singleton bean

context