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

During setter-based field injection, Method.invoke can throw IllegalAccessException when the setter method is not accessible from the engine's reflection context (non-public setter, restrictive SecurityManager, or package-private class). ClassDelegate wraps it in this ActivitiException with the field and class names.

Source

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

            }
        }
    }

    public static void applyFieldDeclaration(FieldDeclaration declaration, Object target) {
        applyFieldDeclaration(declaration, target, true);
    }

    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()

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Make the setter public on the delegate class.
  2. Alternatively rely on direct field injection by making the field non-final and accessible, ensuring fieldTypeCompatible passes.
  3. Check SecurityManager/classloader policy if the setter is already public.

Example fix

// before
void setRecipient(String recipient) { this.recipient = recipient; }
// after
public void setRecipient(String recipient) { this.recipient = recipient; }
Defensive patterns

Strategy: validation

Validate before calling

Method m = delegate.getClass().getMethod("set" + capitalize(fieldName), valueType);
if (!Modifier.isPublic(m.getModifiers())) throw new IllegalStateException(fieldName + " setter must be public");

Type guard

boolean hasPublicSetter(Class<?> c, String field, Class<?> type) {
  try { return Modifier.isPublic(c.getMethod("set" + Character.toUpperCase(field.charAt(0)) + field.substring(1), type).getModifiers()); }
  catch (NoSuchMethodException e) { return false; }
}

Try / catch

try {
  applyFieldDeclaration(declaration, target, throwExceptionOnMissingField);
} catch (ActivitiException e) {
  if (e.getMessage().startsWith("Illegal access")) {
    log.error("Setter for field {} on {} is not accessible", declaration.getName(), target.getClass(), e);
  } else throw e;
}

Prevention

When it happens

Trigger: The resolved delegate class has a setter that matches the field declaration name but is private/protected/package-private, or a security policy blocks reflective access to the member.

Common situations: Delegate class written with a non-public setter (Lombok @Setter(AccessLevel.PROTECTED), hand-written private setter); delegate deployed in a sealed/isolated classloader; Java module/SecurityManager restrictions in hardened environments.

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