spring-projects/spring-framework · error · InvalidPropertyException

Property referenced in indexed property path

Error message

Property referenced in indexed property path '{tokens.canonicalName}' is neither an array nor a List nor a Map; returned value was [{propValue}]

What it means

Thrown by processKeyedProperty's terminal else when a keyed/indexed write is attempted but the resolved property value is not an array, List, or Map. Since there is no container to address with a key/index, Spring rejects the write as InvalidPropertyException.

Solutions

  1. Change the target property to an array/List/Map if indexed access is intended.
  2. Fix the binding path to drop the index/key if the property is scalar.
  3. Verify the property type via wrapper.getPropertyType("name") before issuing the indexed write.
  4. Update the form/binding source to match the actual bean shape.

Example fix

// before
// bean: private String name;  (scalar)
wrapper.setPropertyValue("name[0]", "A");

// after
// bean: private List<String> names = new ArrayList<>();
wrapper.setPropertyValue("names[0]", "A");
Defensive patterns

Strategy: type-guard

Validate before calling

BeanWrapper w = new BeanWrapperImpl(bean);
Class<?> type = w.getPropertyType("name");
if (type == null || !(type.isArray() || List.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type))) {
    throw new IllegalArgumentException("'name' is not indexable: " + type);
}
w.setPropertyValue("name[0]", value);

Type guard

static boolean isIndexableType(Class<?> t) {
    return t != null && (t.isArray() || List.class.isAssignableFrom(t) || Map.class.isAssignableFrom(t));
}

Try / catch

try {
    wrapper.setPropertyValue(path, value);
} catch (InvalidPropertyException ex) {
    if (ex.getMessage().contains("neither an array nor a List nor a Map")) { /* drop the index or change type */ }
    else throw ex;
}

Prevention

When it happens

Trigger: setProperty("name[0]", value) or setProperty("name[x]", value) where 'name' resolves to a scalar (String, Integer, custom object) rather than a collection/array/map.

Common situations: Mismatched binding where the client treats a scalar field as indexed; the property was refactored from a collection to a scalar (or vice-versa) without updating the binder; binding 'field[0]' onto a String property expecting character semantics.

Related errors


AI-assisted analysis of spring-projects/spring-framework@69bf83ad71 (2026-08-09). Data as JSON: /api/errors/0436f091d354c8fa. Report an issue: GitHub.

Appendix: source

Thrown at spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java:354

			TypeDescriptor mapKeyType = ph.getMapKeyType(tokens.keys.length);
			TypeDescriptor mapValueType = ph.getMapValueType(tokens.keys.length);
			// IMPORTANT: Do not pass full property name in here - property editors
			// must not kick in for map keys but rather only for map values.
			Object convertedMapKey = convertIfNecessary(null, null, lastKey,
					mapKeyType.getResolvableType().resolve(), mapKeyType);
			Object oldValue = null;
			if (isExtractOldValueForEditor()) {
				oldValue = map.get(convertedMapKey);
			}
			// Pass full property name and old value in here, since we want full
			// conversion ability for map values.
			Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
					mapValueType.getResolvableType().resolve(), mapValueType);
			map.put(convertedMapKey, convertedMapValue);
		}

		else {
			throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
					"Property referenced in indexed property path '" + tokens.canonicalName +
					"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
		}
	}

	private Object getPropertyHoldingValue(PropertyTokenHolder tokens) {
		// Apply indexes and map keys: fetch value for all keys but the last one.
		Assert.state(tokens.keys != null, "No token keys");
		PropertyTokenHolder getterTokens = new PropertyTokenHolder(tokens.actualName);
		getterTokens.canonicalName = tokens.canonicalName;
		getterTokens.keys = new String[tokens.keys.length - 1];
		System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);

		Object propValue;
		try {
			propValue = getPropertyValue(getterTokens);
		}
		catch (NotReadablePropertyException ex) {

View on GitHub (pinned to 69bf83ad71)