flowable/flowable-engine · error · FlowableIllegalArgumentException

decision tenantId is null

Error message

decision tenantId is null

What it means

DmnDecisionQuery.decisionTenantId(String) throws FlowableIllegalArgumentException("decision tenantId is null") when the tenantId argument is null. Multi-tenant filtering requires a concrete tenant identifier to compare against the TENANT_ID_ column; null is considered invalid input rather than 'match all'.

Source

Thrown at modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/DecisionQueryImpl.java:245

    protected void checkVersion(Integer version) {
        if (version == null) {
            throw new FlowableIllegalArgumentException("version is null");
        } else if (version <= 0) {
            throw new FlowableIllegalArgumentException("version must be positive");
        }
    }

    @Override
    public DecisionQueryImpl latestVersion() {
        this.latest = true;
        return this;
    }

    @Override
    public DmnDecisionQuery decisionTenantId(String tenantId) {
        if (tenantId == null) {
            throw new FlowableIllegalArgumentException("decision tenantId is null");
        }
        this.tenantId = tenantId;
        return this;
    }

    @Override
    public DmnDecisionQuery decisionTenantIdLike(String tenantIdLike) {
        if (tenantIdLike == null) {
            throw new FlowableIllegalArgumentException("decision tenantId is null");
        }
        this.tenantIdLike = tenantIdLike;
        return this;
    }

    @Override
    public DmnDecisionQuery decisionWithoutTenantId() {
        this.withoutTenantId = true;
        return this;

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Guard the call: only apply decisionTenantId when the tenant id is non-null; otherwise run a tenant-agnostic query.
  2. Resolve the tenant from the correct source (security context/request header) and fail fast with a clear message if absent.
  3. If you intended a pattern match, use decisionTenantIdLike with a non-null pattern.

Example fix

// before
query.decisionTenantId(tenantContext.getTenantId()); // may be null
// after
String tenantId = tenantContext.getTenantId();
if (tenantId != null) {
    query.decisionTenantId(tenantId);
}
Defensive patterns

Strategy: validation

Validate before calling

String tenantId = tenantContext.getTenantId();
if (tenantId == null || tenantId.isEmpty()) {
    throw new IllegalStateException("no tenant in context; cannot run tenant-scoped query");
}
query.decisionTenantId(tenantId);

Type guard

boolean hasTenant(String t) { return t != null && !t.isEmpty(); }

Try / catch

try {
    query.decisionTenantId(tenantId);
} catch (FlowableIllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("tenantId")) {
        // fall back to tenant-agnostic query
    } else { throw e; }
}

Prevention

When it happens

Trigger: Calling decisionTenantId(null) — usually the tenant id comes from a security context, request header, or config that is unavailable (anonymous requests, missing tenant provider, standalone engine setup without tenant support configured).

Common situations: Applications adding multi-tenancy later where some code paths have no TenantContext; tests running outside the tenant resolver; misconfigured tenant propagation (header name mismatch) leaving the variable null.

Related errors


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