{"record":{"id":"e025bcc402c551c1","repo":"flowable/flowable-engine","slug":"variable-operation-is-missing-for-variable-e025bc","errorCode":null,"errorMessage":"Variable operation is missing for variable: ","messagePattern":"Variable operation is missing for variable: ","errorType":"http","errorClass":"FlowableIllegalArgumentException","httpStatus":400,"severity":"error","filePath":"modules/flowable-rest/src/main/java/org/flowable/rest/service/api/history/HistoricVariableInstanceBaseResource.java","lineNumber":102,"sourceCode":"\n        }\n\n        if (queryRequest.getVariables() != null) {\n            addVariables(query, queryRequest.getVariables());\n        }\n        \n        if (restApiInterceptor != null) {\n            restApiInterceptor.accessHistoryVariableInfoWithQuery(query, queryRequest);\n        }\n\n        return paginateList(allRequestParams, queryRequest, query, \"variableName\", allowedSortProperties,\n                restResponseFactory::createHistoricVariableInstanceResponseList);\n    }\n\n    protected void addVariables(HistoricVariableInstanceQuery variableInstanceQuery, List<QueryVariable> variables) {\n        for (QueryVariable variable : variables) {\n            if (variable.getVariableOperation() == null) {\n                throw new FlowableIllegalArgumentException(\"Variable operation is missing for variable: \" + variable.getName());\n            }\n            if (variable.getValue() == null) {\n                throw new FlowableIllegalArgumentException(\"Variable value is missing for variable: \" + variable.getName());\n            }\n\n            boolean nameLess = variable.getName() == null;\n\n            Object actualValue = restResponseFactory.getVariableValue(variable);\n\n            // A value-only query is only possible using equals-operator\n            if (nameLess) {\n                throw new FlowableIllegalArgumentException(\"Value-only query (without a variable-name) is not supported\");\n            }\n\n            switch (variable.getVariableOperation()) {\n\n            case EQUALS:\n                variableInstanceQuery.variableValueEquals(variable.getName(), actualValue);","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/history/HistoricVariableInstanceBaseResource.java#L84-L120","documentation":"FlowableIllegalArgumentException thrown by addVariables when a QueryVariable in the 'variables' query parameter lacks a variableOperation (equals/greaterThan/etc.). The historic variable query endpoint builds predicate clauses from each variable entry, and an operation is mandatory to know how to compare values. The request is rejected as a bad client request.","triggerScenarios":"GET /history/historic-variable-instances?variables=[{\"name\":\"status\",\"value\":\"active\"}] (or POST with the same JSON body) — the variable object omits the 'operation' field, or the JSON string is malformed such that operation deserializes to null.","commonSituations":"Hand-built query strings missing 'operation'; client library that maps variable filters but drops the operation attribute; JSON-in-URL parameter mangled by insufficient URL encoding so the operation key is lost.","solutions":["Add the operation field to each variable entry, e.g. variables=[{\"name\":\"status\",\"value\":\"active\",\"operation\":\"EQUALS\"}]","URL-encode the JSON when passing it in the query string so keys are not stripped","Check the client-side query builder actually serializes the operation attribute","Use the documented operation values (EQUALS, NOT_EQUALS, GREATER_THAN, ...) matching QueryVariableOperation"],"exampleFix":"// before\nGET /history/historic-variable-instances?variables=[{\"name\":\"status\",\"value\":\"active\"}]\n// after\nGET /history/historic-variable-instances?variables=%5B%7B%22name%22%3A%22status%22%2C%22value%22%3A%22active%22%2C%22operation%22%3A%22EQUALS%22%7D%5D","handlingStrategy":"validation","validationCode":"clauses.forEach(c => { if (!c.operation) throw new Error(`operation missing for variable ${c.name}`); });\nconst qs = encodeURIComponent(JSON.stringify(clauses));","typeGuard":"function hasOperation(c) { return c && typeof c.operation === 'string' && c.operation.length > 0; }","tryCatchPattern":"try { return await queryHistoricVariables(clauses); } catch (e) { if (String(e.message).includes('Variable operation is missing')) { console.error('malformed clause', clauses); } throw e; }","preventionTips":["Always build variable clauses through a helper that requires {name, value, operation}","URL-encode the variables JSON in query strings","Keep operation values aligned with QueryVariableOperation enum names"],"tags":["rest","flowable","query","validation","bad-request"],"backgroundTag":"missing-required-argument","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}