flowable/flowable-engine · error · ActivitiException

Illegal access when calling '%s' on class %s

Error message

Illegal access when calling '%s' on class %s

What it means

Thrown when applying a BPMN field injection declaration to a service task delegate via a setter method: the underlying setter's Method.invoke() raised IllegalAccessException, meaning the setter is not accessible (e.g. non-public) from the engine's reflection call. The engine wraps it in an ActivitiException naming the field and target class.

Source

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

    public static void applyFieldDeclaration(List<FieldDeclaration> fieldDeclarations, Object target) {
        if (fieldDeclarations != null) {
            for (FieldDeclaration declaration : fieldDeclarations) {
                applyFieldDeclaration(declaration, target);
            }
        }
    }

    public static void applyFieldDeclaration(FieldDeclaration declaration, Object target) {
        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) {
                throw new ActivitiIllegalArgumentException("Field definition uses unexisting field '" + declaration.getName() + "' on class " + target.getClass().getName());
            }
            // 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());
        }
    }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Make the setter method public on the delegate class
  2. Verify the flowable:field name attribute exactly matches an existing public setter (setXxx)
  3. Alternatively switch to direct field injection: make the target field public or use field name matching a public field
  4. Check the target class is the actual delegate class (not a subclass without the setter)

Example fix

// before
public class MyDelegate implements JavaDelegate {
    private String value;
    void setValue(String value) { this.value = value; }
}
// after
public class MyDelegate implements JavaDelegate {
    private String value;
    public void setValue(String value) { this.value = value; }
}
Defensive patterns

Strategy: validation

Validate before calling

if (java.lang.reflect.Modifier.isPublic(delegate.getClass().getMethod("setValue", String.class).getModifiers())) { /* safe to inject */ }

Prevention

When it happens

Trigger: A delegate class has a setter for an injected field declared in the BPMN XML (flowable:field) that is private/protected/package-private or otherwise not callable via reflection, and ClassDelegateUtil.applyFieldDeclaration invokes it.

Common situations: Hand-written delegates with non-public setters; copying delegate code between packages with different visibility; refactoring a public field into a private setter without making the setter public; proxies or restricted classloaders blocking access.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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