flowable/flowable-engine · error · FlowableIllegalArgumentException
callbackIds is null or empty
Error message
callbackIds is null or empty
What it means
HistoricCaseInstanceQueryImpl.caseInstanceCallbackIds(Set<String>) validates that the callbackIds set is non-null and non-empty before storing it as a query filter. Flowable query implementations reject empty criteria because they would either produce an invalid SQL IN clause or silently match nothing. Throwing FlowableIllegalArgumentException fails fast at query-building time instead of at execution time.
Solutions
- Pass a Set containing at least one non-null callback id string.
- Skip adding the callback-ids filter (or use a different query branch) when the set is empty instead of calling the method.
- If a null/empty set is a valid 'no filter' case in your code, check isEmpty() before building the query and choose the appropriate query variant.
Example fix
// before
query.caseInstanceCallbackIds(callbackIds);
// after
if (callbackIds != null && !callbackIds.isEmpty()) {
query.caseInstanceCallbackIds(callbackIds);
} Defensive patterns
Strategy: validation
Validate before calling
// Java
if (callbackIds == null || callbackIds.isEmpty()) {
throw new IllegalStateException("callbackIds must contain at least one id");
}
query.caseInstanceCallbackIds(callbackIds); Type guard
boolean hasCallbackIds(Set<String> s) { return s != null && !s.isEmpty(); } Try / catch
try {
query.caseInstanceCallbackIds(callbackIds);
} catch (FlowableIllegalArgumentException e) {
// fall back to query without callback-id filter
log.warn("Ignoring callbackIds filter: {}", e.getMessage());
} Prevention
- Never pass collections straight from upstream sources into Flowable query filters without an isEmpty() check.
- Centralize query building in a helper that validates all filter inputs.
- When migrating from single-id to set-based filter methods, check the set is populated.
When it happens
Trigger: Calling caseInstanceCallbackIds(null) or caseInstanceCallbackIds(new HashSet<>()) (any empty Set) on a HistoricCaseInstanceQuery, directly or inside an or() block.
Common situations: Collecting callback ids from an upstream list that turned out empty and passing the collection straight into the query; a variable initialized to an empty set and never populated; refactoring that replaced a single callbackId call with the plural set-based API.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Business status is null
- callback type is null
- Case definition keys is null
- caseInstance tenant id is null
- parent case instance id is null
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/f06f0a2233e79a9e.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/history/HistoricCaseInstanceQueryImpl.java:744
}
@Override
public HistoricCaseInstanceQuery caseInstanceCallbackId(String callbackId) {
if (callbackId == null) {
throw new FlowableIllegalArgumentException("callback id is null");
}
if (inOrStatement) {
this.currentOrQueryObject.callbackId = callbackId;
} else {
this.callbackId = callbackId;
}
return this;
}
@Override
public HistoricCaseInstanceQuery caseInstanceCallbackIds(Set<String> callbackIds) {
if (callbackIds == null || callbackIds.isEmpty()) {
throw new FlowableIllegalArgumentException("callbackIds is null or empty");
}
if (inOrStatement) {
this.currentOrQueryObject.callbackIds = callbackIds;
} else {
this.callbackIds = callbackIds;
}
return this;
}
@Override
public HistoricCaseInstanceQuery caseInstanceCallbackType(String callbackType) {
if (callbackType == null) {
throw new FlowableIllegalArgumentException("callback type is null");
}
if (inOrStatement) {
this.currentOrQueryObject.callbackType = callbackType;
} else {
this.callbackType = callbackType;View on GitHub (pinned to d6d39ce1c6)