flowable/flowable-engine · error · ActivitiIllegalArgumentException

Field definition uses unexisting field

Error message

Field definition uses unexisting field '%s' on class %s

What it means

When no setter matches the field declaration name, ClassDelegate falls back to direct field injection. If neither a setter nor a field with that name exists on the delegate class and throwExceptionOnMissingField is true (default), an ActivitiIllegalArgumentException is thrown naming the missing field and class. This catches typos in activiti:field declarations at first use.

Solutions

  1. Fix the field name in the BPMN XML (or delegateExpression config) to match an existing setter/field on the delegate class.
  2. Add the missing setter/field to the delegate class.
  3. If the field is intentionally optional, construct ClassDelegate with throwExceptionOnMissingField=false.

Example fix

// before (BPMN)
<activiti:field name="receipent"><activiti:string>x</activiti:string></activiti:field>
// after
<activiti:field name="recipient"><activiti:string>x</activiti:string></activiti:field>
// matching: public void setRecipient(String recipient)
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = delegateClass;
boolean ok = Arrays.stream(c.getMethods()).anyMatch(m -> m.getName().equals("set" + capitalize(fieldName)))
          || Arrays.stream(c.getDeclaredFields()).anyMatch(f -> f.getName().equals(fieldName));
if (!ok) throw new IllegalStateException("Field '" + fieldName + "' missing on " + c.getName());

Type guard

boolean fieldExists(Class<?> c, String name) {
  try { c.getDeclaredField(name); return true; }
  catch (NoSuchFieldException e) {
    return Arrays.stream(c.getMethods()).anyMatch(m -> m.getName().equalsIgnoreCase("set" + name));
  }
}

Try / catch

try {
  applyFieldDeclaration(declaration, target, true);
} catch (ActivitiIllegalArgumentException e) {
  if (e.getMessage().contains("unexisting field")) {
    log.error("activiti:field name '{}' does not exist on {}", declaration.getName(), target.getClass(), e);
  } else throw e;
}

Prevention

When it happens

Trigger: BPMN XML declares <activiti:field name="foo"> but the delegate class has neither setFoo(...) nor a member foo; ClassDelegate was constructed with throwExceptionOnMissingField=true.

Common situations: Typo in the field name in process XML; delegate refactored/renamed the field or setter while old process definitions still deployed; copying field declarations between delegates with different property names.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

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

    public static void applyFieldDeclaration(FieldDeclaration declaration, Object target, boolean throwExceptionOnMissingField) {
        Method setterMethod = ReflectUtil.getSetter(declaration.getName(),
                target.getClass(), declaration.getValue().getClass());

        if (setterMethod != null) {
            try {
                setterMethod.invoke(target, declaration.getValue());
            } catch (IllegalArgumentException e) {
                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) {

View on GitHub (pinned to d6d39ce1c6)