hibernate/hibernate-orm · error · IllegalArgumentException

Expected array or collection value, but got:

Error message

Expected array or collection value, but got: 

What it means

JsonGeneratingVisitor.visitArray accepts only Java arrays and java.util.Collection values when serializing an aggregate array element. If the runtime value is neither - a singular object, a wrapper, or an Iterator - it throws IllegalArgumentException naming the actual class.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/spi/JsonGeneratingVisitor.java:97

					visit( embeddableMappingType, Array.get( values, j ), options, writer );
				}
				catch (IOException e) {
					throw new IllegalArgumentException( "Could not serialize array element", e );
				}
			}
		}
		else if ( values instanceof Collection<?> collection ) {
			for ( final var item : collection ) {
				try {
					visit( embeddableMappingType, item, options, writer );
				}
				catch (IOException e) {
					throw new IllegalArgumentException( "Could not serialize array element", e );
				}
			}
		}
		else {
			throw new IllegalArgumentException(
					"Expected array or collection value, but got: " + values.getClass().getName()
			);
		}
	}

	private void visitBasicPluralValues(
			JavaType<?> elementJavaType,
			JdbcType elementJdbcType,
			Object values,
			WrapperOptions options,
			JsonDocumentWriter writer) {
		if ( values.getClass().isArray() ) {
			final var length = Array.getLength( values );
			for ( int j = 0; j < length; j++ ) {
				final var item = Array.get( values, j );
				if ( item == null ) {
					writer.nullValue();
				}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Make the custom JavaType/converter return an actual array or Collection for aggregate array values.
  2. Fix the attribute mapping so the declared element type matches the runtime value type.
  3. If the property is genuinely singular, map it as a plain aggregate (not an array aggregate).

Example fix

// before: custom plural JavaType returns a scalar
public Object wrap(Object value, WrapperOptions o) {
    return ((Ids) value).first();      // -> Expected array or collection value
}
// after: return the plural form
public Object wrap(Object value, WrapperOptions o) {
    return ((Ids) value).toList();     // List/Array is accepted
}
Defensive patterns

Strategy: type-guard

Validate before calling

static boolean isPluralValue(Object value) {
    return value == null || value instanceof Collection<?> || value.getClass().isArray();
}

Type guard

static boolean canBindAsAggregateArray(Object value) {
    return value == null || value instanceof Object[]
        || value instanceof Collection<?> || value.getClass().isArray();
}

Try / catch

try {
    session.persist(order);
} catch (IllegalArgumentException ex) {
    if (ex.getMessage() != null && ex.getMessage().startsWith("Expected array or collection value")) {
        throw new IllegalStateException(
            "Aggregate array attribute holds a non-plural value; fix the JavaType/converter", ex);
    }
    throw ex;
}

Prevention

When it happens

Trigger: The JavaType of an array-typed aggregate element hands a non-plural value to the serializer: a custom BasicPluralJavaType or JavaType whose wrap/unwrap returns a scalar, an AttributeConverter that strips the collection, or an element mapping whose declared type does not match the stored value type.

Common situations: Custom plural JavaTypes for array columns; converters applied to aggregate array attributes; mapping an attribute as an array aggregate while the domain property is singular.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/58152752a40b6ee1. Report an issue: GitHub.