kestra-io/kestra · error · IllegalArgumentException
QueryFilter must be either a leaf (field + operation) or a…
Error message
QueryFilter must be either a leaf (field + operation) or a node (logical + non-empty children), not both or neither
What it means
Thrown by the QueryFilter JsonCreator constructor when the supplied JSON does not match exactly one of two permitted shapes: a 'leaf' (field + operation set, logical and children null) or a 'node' (logical + non-empty children set, field/operation/value null). Mixing the two, supplying a node with empty children, or omitting required fields all trip this guard. It is the structural invariant that lets the query builder safely distinguish composite filters from terminal predicates.
Solutions
- Ensure leaf filters contain ONLY field, operation, and value (no logical/children).
- Ensure composite nodes contain ONLY logical and a non-empty children array (no field/operation/value).
- Validate the filter JSON shape client-side before sending.
- If migrating old filter shapes, normalize them into the leaf-or-node structure first.
Example fix
// before
{"field":"namespace","operation":"EQUAL_TO","value":"prod","logical":"AND","children":[]}
// IllegalArgumentException
// after (leaf)
{"field":"namespace","operation":"EQUAL_TO","value":"prod"}
// after (node)
{"logical":"AND","children":[{"field":"namespace","operation":"EQUAL_TO","value":"prod"}]} Defensive patterns
Strategy: validation
Validate before calling
boolean leaf = field != null && operation != null && logical == null && (children == null || children.isEmpty());
boolean node = logical != null && children != null && !children.isEmpty() && field == null && operation == null && value == null;
if (!leaf && !node) {
throw new IllegalArgumentException("Invalid QueryFilter shape");
} Type guard
// Type guard narrowing a raw filter object
function isLeafFilter(f): f is { field: string; operation: string; value: unknown } {
return !!f.field && !!f.operation && !f.logical && !f.children;
}
function isNodeFilter(f): f is { logical: string; children: unknown[] } {
return !!f.logical && Array.isArray(f.children) && f.children.length > 0 && !f.field && !f.operation;
} Try / catch
try {
new QueryFilter(field, operation, value, logical, children);
} catch (IllegalArgumentException e) {
// return 400 with the structural requirement
throw new InvalidQueryFiltersException(List.of(e.getMessage()));
} Prevention
- Build filters with a factory that enforces leaf-or-node invariants.
- Never partially populate both shapes in API payloads.
When it happens
Trigger: POSTing a dashboard/query filter JSON that includes both 'field' and 'logical'; providing a 'logical' with a null or empty 'children' array; providing 'field' without 'operation'; providing 'children' but no 'logical'.
Common situations: Frontend builds a filter object dynamically and leaves stray keys; API client serializes a partial filter; a migration of an old filter shape leaves inconsistent fields.
Related errors
- Provided query filters are invalid
- Invalid json
- Select option object must define a `value` field
- The 'fromJson' function expects an argument 'json'.
- The 'fromJson' function expects an argument 'json' with…
AI-assisted analysis of kestra-io/kestra@823fada927 (2026-08-14).
Data as JSON: /api/errors/45e6626054fe5058.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/io/kestra/core/models/QueryFilter.java:40
public record QueryFilter(
Field field,
Op operation,
Object value,
Logical logical,
List<QueryFilter> children) {
@JsonCreator
public QueryFilter(
@JsonProperty("field") Field field,
@JsonProperty("operation") Op operation,
@JsonProperty("value") Object value,
@JsonProperty("logical") Logical logical,
@JsonProperty("children") List<QueryFilter> children) {
boolean leafShape = field != null && operation != null && logical == null && children == null;
boolean nodeShape = logical != null && children != null && !children.isEmpty()
&& field == null && operation == null && value == null;
if (!leafShape && !nodeShape) {
throw new IllegalArgumentException(
"QueryFilter must be either a leaf (field + operation) or a node (logical + non-empty children), not both or neither"
);
}
this.field = field;
this.operation = operation;
this.value = value;
this.logical = logical;
this.children = children;
}
public boolean isLeaf() {
return logical == null;
}
public boolean isNode() {
return logical != null;
}
View on GitHub (pinned to 823fada927)