flowable/flowable-engine · error · FlowableIllegalArgumentException

Incompatible type set on field declaration '<fieldName>' for

Error message

Incompatible type set on field declaration '<fieldName>' for class <className>. Declared value has type <declaredType>, while expecting <expectedType>

What it means

Thrown by ClassDelegateUtil.applyFieldDeclaration as a FlowableIllegalArgumentException when the injected value's Java type is not compatible with the declared field's type (fieldTypeCompatible returns false). The message states both the declared value type and the expected field type.

Source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/helper/ClassDelegateUtil.java:68

        if (setterMethod != null) {
            try {
                setterMethod.invoke(target, declaration.getValue());
            } catch (IllegalArgumentException e) {
                throw new FlowableException("Error while invoking '" + declaration.getName() + "' on class " + target.getClass().getName(), e);
            } catch (IllegalAccessException e) {
                throw new FlowableException("Illegal access when calling '" + declaration.getName() + "' on class " + target.getClass().getName(), e);
            } catch (InvocationTargetException e) {
                throw new FlowableException("Exception while invoking '" + declaration.getName() + "' on class " + target.getClass().getName(), e);
            }
        } else {
            Field field = ReflectUtil.getField(declaration.getName(), target);
            if (field == null) {
                throw new FlowableIllegalArgumentException("Field definition uses non-existing field '" + declaration.getName() + "' on class " + target.getClass().getName());
            }
            // Check if the delegate field's type is correct
            if (!fieldTypeCompatible(declaration, field)) {
                throw new FlowableIllegalArgumentException("Incompatible type set on field declaration '" + declaration.getName() + "' for class " + target.getClass().getName() + ". Declared value has type "
                        + declaration.getValue().getClass().getName() + ", while expecting " + field.getType().getName());
            }
            ReflectUtil.setField(field, target, declaration.getValue());
        }
    }

    public static boolean fieldTypeCompatible(FieldDeclaration declaration, Field field) {
        if (declaration.getValue() != null) {
            return field.getType().isAssignableFrom(declaration.getValue().getClass());
        } else {
            // Null can be set any field type
            return true;
        }
    }

}

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Align the BPMN field declaration type with the delegate field type (use the correct stringValue/expression element)
  2. Change the delegate field type to accept the injected value (e.g. String, or a type with a matching converter)
  3. Use <flowable:field><flowable:value> with a serialized/custom value matching the field type
  4. Add an explicit type check or conversion in the delegate's setter

Example fix

// before (field is int timeout)
<flowable:field name="timeout" stringValue="10"/>
// after
<flowable:field name="timeout"><flowable:value>10</flowable:value></flowable:field>
// or change delegate field to String timeout
Defensive patterns

Strategy: validation

Validate before calling

Field f = delegateClass.getDeclaredField(fieldName);
Object v = resolveDeclaredValue(declaration);
if (v != null && !f.getType().isAssignableFrom(v.getClass())) {
    throw new ConfigException("Field " + fieldName + " expects " + f.getType() + " but got " + v.getClass());
}

Type guard

static boolean fieldTypeOk(Class<?> delegate, String name, Object value) throws NoSuchFieldException {
    Class<?> t = delegate.getDeclaredField(name).getType();
    return value == null || t.isInstance(value) || t.isPrimitive() && isBoxCompatible(t, value.getClass());
}

Try / catch

try { startProcess(key, vars); }
catch (FlowableIllegalArgumentException e) {
    if (e.getMessage().contains("Incompatible type set on field declaration")) {
        log.error("Field injection type mismatch: {}", e.getMessage());
    } else throw e;
}

Prevention

When it happens

Trigger: A <flowable:field> supplies a stringValue/expression whose runtime type (e.g. String) does not match the delegate field type (e.g. int, List, custom type), so the compatibility check fails before ReflectUtil.setField.

Common situations: Injecting a string into an Integer/List field without a matching type in the declaration; using expression results of unexpected type; switching a field's type in the delegate without updating process XML; confusion between stringValue, expression and object param types.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/83037c58c4ad8afb. Report an issue: GitHub.