flowable/flowable-engine · error · ActivitiIllegalArgumentException

Incompatible type set on field declaration

Error message

Incompatible type set on field declaration '%s' for class %s. Declared value has type %s, while expecting %s

What it means

After locating the target field for direct injection, ClassDelegate validates that the declared value's type is assignable to the field's declared type via fieldTypeCompatible(). If not, it throws ActivitiIllegalArgumentException with both type names. This prevents ClassCastException later when the delegate reads the field.

Solutions

  1. Change the declared value type in the BPMN XML so it matches the field type (e.g. use activiti:expression or the correct element).
  2. Widen the delegate field type (e.g. accept Expression or String and convert in execute()).
  3. Add a setter with a compatible parameter type so setter injection is used instead of direct field injection.

Example fix

// before
<activiti:field name="retries"><activiti:string>3</activiti:string></activiti:field>
// delegate field: private int retries;
// after
<activiti:field name="retries"><activiti:expression>3</activiti:expression></activiti:field>
// or change field to: private String retries;
Defensive patterns

Strategy: validation

Validate before calling

Field f = delegateClass.getDeclaredField(fieldName);
if (!fieldTypeCompatible(declaredValue.getClass(), f.getType())) {
  throw new IllegalStateException("Declared " + declaredValue.getClass() + " incompatible with " + f.getType());
}

Type guard

boolean fieldTypeCompatible(Object value, Field field) {
  return value == null || field.getType().isInstance(value) || Expression.class.isAssignableFrom(field.getType());
}

Try / catch

try {
  applyFieldDeclaration(declaration, target, throwExceptionOnMissingField);
} catch (ActivitiIllegalArgumentException e) {
  if (e.getMessage().contains("Incompatible type")) {
    log.error("Field '{}' type mismatch on {}", declaration.getName(), target.getClass(), e);
  } else throw e;
}

Prevention

When it happens

Trigger: A field declaration sets a value whose Java type is incompatible with the delegate's field type - e.g. injecting a string value into a field of type int, Date, Expression, or a custom POJO where no conversion applies.

Common situations: activiti:field with <activiti:string> into an int/long/boolean field; injecting an expression string into an Expression-typed field without the expected wrapper; schema/version change of delegate field type while old process XML is reused.

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/139f3ccdbe275c2b. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/bpmn/helper/ClassDelegate.java:289

                throw new ActivitiException("Error while invoking '" + declaration.getName() + "' on class " + target.getClass().getName(), e);
            } catch (IllegalAccessException e) {
                throw new ActivitiException("Illegal access when calling '" + declaration.getName() + "' on class " + target.getClass().getName(), e);
            } catch (InvocationTargetException e) {
                throw new ActivitiException("Exception while invoking '" + declaration.getName() + "' on class " + target.getClass().getName(), e);
            }
        } else {
            Field field = ReflectUtil.getField(declaration.getName(), target);
            if (field == null) {
                if (throwExceptionOnMissingField) {
                    throw new ActivitiIllegalArgumentException("Field definition uses unexisting field '" + declaration.getName() + "' on class " + target.getClass().getName());
                } else {
                    return;
                }
            }

            // Check if the delegate field's type is correct
            if (!fieldTypeCompatible(declaration, field)) {
                throw new ActivitiIllegalArgumentException("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)