Explain the legacy PropertyEditor mechanism, why it was superseded by the ConversionService/Formatter SPI, and how the two coexist.
answer
- PropertyEditor = JavaBeans, setAsText/getAsText + setValue/getValue
- stateful → NOT thread-safe → one per binding
- String-only, no TypeDescriptor/annotations/locale
- Spring 3 core.convert replaced it (stateless, shareable)
- coexist: ConversionService first, editors as fallback; @InitBinder to register
basics
~20 sPropertyEditor 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 sjava.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// 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
Know PropertyEditor is the old String↔Object mechanism and Converters are the modern replacement.
Explain setAsText/getAsText, statefulness, and @InitBinder registration.
Articulate the exact limitations that motivated core.convert and how the two coexist with precedence.
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