Why Does #{array.length} Trigger a ReflectionException in MyBatis?

This article dissects the root cause of the ReflectionException thrown when using #{array.length} in MyBatis XML, explains how MyBatis parses XML tags, OGNL expressions, and parameter placeholders, and presents three practical solutions to avoid the error.

Beike Product & Technology
Beike Product & Technology
Beike Product & Technology
Why Does #{array.length} Trigger a ReflectionException in MyBatis?

Problem Overview

A ReflectionException is thrown when an array length is accessed with #{ids.length} in a MyBatis <select> statement. The exception occurs during parameter placeholder parsing, not during the evaluation of the test condition.

XML Parsing in MyBatis

The class org.apache.ibatis.scripting.xmltags.XMLScriptBuilder builds a map of NodeHandler implementations for each XML tag ( if, foreach, where, etc.). When a tag is encountered, the corresponding handler creates a SqlNode object that will be executed later.

private class IfHandler implements NodeHandler {
    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, nodeToHandle.getStringAttribute("test"));
        targetContents.add(ifSqlNode);
    }
}

The IfSqlNode evaluates the test expression using ExpressionEvaluator, which relies on OGNL.

Parameter Placeholder Parsing ( #{} )

When MyBatis encounters a #{property} placeholder it creates a MetaObject for the parameter object and uses an ObjectWrapper to read the property value via reflection.

Wrap the parameter object:

MetaObject meta = configuration.newMetaObject(parameterObject);

Select an appropriate ObjectWrapper ( BeanWrapper, MapWrapper, CollectionWrapper, ArrayWrapper).

Split nested properties with PropertyTokenizer (e.g., bean.prop[0].name).

Recursively retrieve the value via MetaObject.getValue() or ObjectWrapper.get().

For a plain bean the BeanWrapper calls the getter method. If the object is an array, there is no getLength() method, so the wrapper throws the ReflectionException.

Why #{array.length} Fails

During step 3 the parser treats ids.length as a bean property named length. The BeanWrapper therefore tries to invoke getLength() on the array object, which does not exist, leading to the exception. The test attribute works because it is evaluated by OGNL, which can read the array length directly.

Solutions

Use OGNL syntax : replace #{ids.length} with ${ids.length}. The expression is evaluated by OGNL and the result is inserted into the SQL string.

Bind the length to a variable and use the variable in the LIMIT clause:

<bind name="idCount" value="ids.length"/>
LIMIT #{idCount}

Provide a custom ArrayWrapper that supports a length property. Example implementation:

public class ArrayWrapper implements ObjectWrapper {
    private final Object array;
    public ArrayWrapper(MetaObject metaObject, Object object) {
        if (!object.getClass().isArray()) {
            throw new IllegalArgumentException("object must be an array");
        }
        this.array = object;
    }
    @Override
    public Object get(PropertyTokenizer prop) {
        if ("length".equals(prop.getName())) {
            return Array.getLength(array);
        }
        throw new UnsupportedOperationException();
    }
    // other methods throw UnsupportedOperationException
}

Register this wrapper so that MyBatis creates it for array parameters, allowing #{ids.length} to work.

How OGNL Is Used in if and foreach

The ExpressionEvaluator evaluates the test attribute:

if (evaluator.evaluateBoolean(test, context.getBindings())) {
    contents.apply(context);
    return true;
}
return false;

For foreach, OGNL obtains an Iterable from the collection expression:

final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);

MetaObject Property Retrieval Flow

The core method is MetaObject.getValue(String name):

public Object getValue(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            return null;
        }
        return metaValue.getValue(prop.getChildren());
    } else {
        return objectWrapper.get(prop);
    }
}

The ObjectWrapper.get(PropertyTokenizer) implementation varies: BeanWrapper uses reflection to call a getter or to access an indexed element. MapWrapper retrieves the value from a Map. CollectionWrapper and ArrayWrapper handle indexed access for collections and arrays.

When the property name is length and the wrapped object is an array, the custom ArrayWrapper returns Array.getLength(array) instead of trying to call a getter.

Additional Notes

${}

expressions are evaluated by OGNL and the result is concatenated into the SQL string; #{} creates a prepared‑statement placeholder.

Primitive arrays cannot be converted to Object[] via Arrays.asList(); a manual iteration (as shown in the ArrayWrapper example) is required.

The default CollectionWrapper does not support property access ( get / set) because collections are not treated as beans.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaMyBatisXMLOGNLReflectionException
Beike Product & Technology
Written by

Beike Product & Technology

As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.