spring-projects/spring-framework · error · NotWritablePropertyException

Invalid property ' ' of bean class [ ]

Error message

Invalid property '{propertyName}' of bean class [{beanClass.getName()}]: {matches.buildErrorMessage()}

What it means

Thrown as NotWritablePropertyException by BeanWrapperImpl.createNotWritablePropertyException when a property name cannot be resolved to a writable property (no matching PropertyDescriptor with a write method). The message includes PropertyMatches.buildErrorMessage() which suggests similar property names, so the exception text usually shows 'Did you mean?' candidates.

Solutions

  1. Use the suggested 'possible matches' from the exception message to correct the property name.
  2. Add a public setter for the property on the target class (or open it for field access if using a field-accessor).
  3. Allow unknown fields if expected: configure the DataBinder with setIgnoreUnknownFields(true) (Spring Boot @ConfigurationProperties) or skip the binding path.

Example fix

// before
wrapper.setPropertyValue("fristName", "Jane"); // typo

// after
wrapper.setPropertyValue("firstName", "Jane");
Defensive patterns

Strategy: validation

Validate before calling

// before binding
Set<String> writable = Arrays.stream(wrapper.getPropertyDescriptors())
    .filter(pd -> pd.getWriteMethod() != null)
    .map(PropertyDescriptor::getName).collect(Collectors.toSet());
if (!writable.contains(name)) {
    throw new IllegalArgumentException("not writable: " + name);
}

Type guard

static boolean isWritable(BeanWrapper w, String name) {
    try { return w.getPropertyDescriptor(name).getWriteMethod() != null; }
    catch (InvalidPropertyException | NotWritablePropertyException e) { return false; }
}

Prevention

When it happens

Trigger: Calling setPropertyValue / setPropertyValues on a BeanWrapperImpl with a property name that does not correspond to a writable bean property of the root class. Triggered from the base AbstractNestablePropertyAccessor when getLocalPropertyHandler returns null and a write is attempted.

Common situations: Typo in a property name in @Value SpEL, DataBinder/ServletRequestDataBinder binding a request parameter that has no setter, @ConfigurationProperties prefix mismatch, or a setter that was removed/renamed.

Related errors


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

Appendix: source

Thrown at spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java:205

		TypeDescriptor td = ((GenericTypeAwarePropertyDescriptor) pd).getTypeDescriptor();
		return convertForProperty(propertyName, null, value, td);
	}

	@Override
	protected @Nullable PropertyHandler getLocalPropertyHandler(String propertyName) {
		PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
		return (pd != null ? new BeanPropertyHandler((GenericTypeAwarePropertyDescriptor) pd) : null);
	}

	@Override
	protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) {
		return new BeanWrapperImpl(object, nestedPath, this);
	}

	@Override
	protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
		PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass());
		throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
				matches.buildErrorMessage(), matches.getPossibleMatches());
	}

	@Override
	public PropertyDescriptor[] getPropertyDescriptors() {
		return getCachedIntrospectionResults().getPropertyDescriptors();
	}

	@Override
	public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException {
		BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName);
		String finalPath = getFinalPath(nestedBw, propertyName);
		PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath);
		if (pd == null) {
			throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName,
					"No property '" + propertyName + "' found");
		}
		return pd;

View on GitHub (pinned to 69bf83ad71)