skip to content

What is BeanWrapper/BeanWrapperImpl, and how does it handle nested, indexed and mapped property paths?

level: middleimportance: should knowfreq 45%

answer

  1. get/set by string name via getters/setters
  2. address.city, orders[0], map[key]
  3. autoGrowNestedPaths default true
  4. autoGrowCollectionLimit 256
  5. implements PropertyAccessor; DataBinder owns one

basics

~10 s

BeanWrapper is Spring's low-level API to get/set JavaBean properties by name via getters/setters. It understands dotted and indexed paths like 'address.city' or 'orders[0].id', and can auto-create missing intermediate objects.

solid answer

~40 s

org.springframework.beans.BeanWrapper (impl BeanWrapperImpl) is a PropertyAccessor that reads and writes a JavaBean's properties reflectively through its getters and setters. Its power is navigating property paths: nested with dots ('address.city'), indexed with brackets on arrays/Lists ('orders[0]'), and mapped for Maps ('attributes[color]'), combinable ('orders[0].line.price'). When you setPropertyValue on a deep path, BeanWrapperImpl has autoGrowNestedPaths=true by default, so it instantiates missing intermediate beans and grows lists/arrays as needed. It also exposes getPropertyType, isReadableProperty/isWritableProperty, and integrates type conversion via registered PropertyEditors or a ConversionService. DataBinder sits on top of BeanWrapper — the wrapper does the actual property access while DataBinder adds error collection, allowed/required fields and validation.

code

java · 15 lines
java
class Address { private String city; /* get/set */ 
    public String getCity(){return city;} public void setCity(String c){city=c;} }
class Person { private Address address; private List<String> tags = new ArrayList<>();
    public Address getAddress(){return address;} public void setAddress(Address a){address=a;}
    public List<String> getTags(){return tags;} public void setTags(List<String> t){tags=t;} }

BeanWrapper bw = new BeanWrapperImpl(new Person());
// address is null; auto-grow creates an Address for us:
bw.setPropertyValue("address.city", "Paris");
// indexed access grows the list:
bw.setPropertyValue("tags[2]", "spring");

Person p = (Person) bw.getWrappedInstance();
System.out.println(p.getAddress().getCity()); // Paris
System.out.println(p.getTags());              // [null, null, spring]

go deeper

for a junior

Know it's a way to set bean properties by string name.

for a middle

Explain nested/indexed/mapped paths and auto-grow default true.

for a senior

Discuss autoGrowCollectionLimit, NullValueInNestedPathException, and the BeanWrapper↔DataBinder split of responsibilities.

for a principal

Frame BeanWrapper as the PropertyAccessor SPI seam that both the DI container (property population) and DataBinder reuse, and reason about its DoS-relevant defaults.

## BeanWrapper in one line `org.springframework.beans.BeanWrapper` is Spring's **reflective JavaBean accessor**: given an object, it lets you get and set properties **by string name** rather than calling getters/setters in code. The standard implementation is `org.springframework.beans.BeanWrapperImpl`. It implements `PropertyAccessor` (the read/write-by-name contract) and `ConfigurablePropertyAccessor` (conversion + config), and adds JavaBean-specific reflection (`PropertyDescriptor`s). ```java BeanWrapper bw = new BeanWrapperImpl(new Person()); bw.setPropertyValue("name", "Ada"); String name = (String) bw.getPropertyValue("name"); ``` Property access goes **through getters and setters** (the JavaBean convention), not directly to fields. (The field-access sibling is `DirectFieldAccessor`.) ## Property paths — the real feature BeanWrapper parses structured path expressions: - **Nested (dot):** `"address.city"` → `person.getAddress().setCity(...)`. - **Indexed (brackets on array/List):** `"orders[0]"` → element 0 of the list/array. - **Mapped (brackets on Map):** `"attributes[color]"` → `map.get("color")` / `put`. - **Combined:** `"orders[0].lineItems[2].price"`. ## Auto-grow of nested paths `BeanWrapperImpl.setAutoGrowNestedPaths` defaults to **true**. Setting a deep path where intermediates are null will **instantiate** the missing beans (requires a default constructor) and **grow** lists/arrays to the needed index. Example: on a fresh `Person`, `bw.setPropertyValue("address.city", "Paris")` first creates a new `Address`, then sets its city. Gotchas: - Auto-instantiation needs a public no-arg constructor; otherwise you get an exception. - `autoGrowCollectionLimit` (default 256) caps how far a collection/array can be grown, to prevent a malicious huge index from exhausting memory. - If you set `setAutoGrowNestedPaths(false)`, hitting a null intermediate throws `NullValueInNestedPathException`. ## Type conversion When the supplied value type doesn't match the property type, BeanWrapper converts it — via registered `PropertyEditor`s or a `ConversionService`. (The conversion machinery itself is a separate concern/leaf; here just note that BeanWrapper is where it gets invoked during set.) ## Useful introspection methods - `getPropertyType(String)` / `getPropertyTypeDescriptor(String)` - `isReadableProperty(String)` / `isWritableProperty(String)` - `getPropertyDescriptors()` ## Relationship to DataBinder `DataBinder` **owns a BeanWrapper** (unless switched to direct field access). BeanWrapper does raw property access and throws `BeansException` subtypes (e.g. `NotWritablePropertyException`, `TypeMismatchException`) on failure; DataBinder wraps those and turns them into recorded `FieldError`s instead of propagating exceptions. ## When to use directly Rarely in app code — it's infrastructure the container and DataBinder use. You might reach for it in generic tooling that needs to read/write arbitrary bean properties by name (mappers, generic form processors, test utilities).

  • What happens if a nested intermediate is null and auto-grow is disabled?
    BeanWrapper throws NullValueInNestedPathException, because it won't instantiate the missing intermediate object for you.
  • Why does autoGrowCollectionLimit exist?
    Security/robustness: without a cap, binding an attacker-supplied index like list[1000000] could force the collection to grow to a million elements and exhaust memory. It defaults to 256.

saying these in an interview costs you the question

  • Claiming BeanWrapper accesses fields directly (it uses getters/setters by default)
  • Saying nested null intermediates are always auto-created without limit (there's autoGrowCollectionLimit and it can be disabled)
  • Confusing indexed [0] on lists with mapped [key] on maps

context