apache/dolphinscheduler · error · ServiceException

WORKFLOW_NODE_HAS_CYCLE

WORKFLOW_NODE_HAS_CYCLE

Error message

WORKFLOW_NODE_HAS_CYCLE: workflow node has cycle

What it means

Thrown by checkWorkflowJsonValidation when graphHasCycle(taskNodes) detects a dependency cycle among the workflow's task nodes. A valid workflow DAG must be acyclic; a cycle would make topological scheduling impossible. The error names the offending workflow implicitly via the request context.

Source

Thrown at dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/WorkflowDefinitionServiceImpl.java:955

            if (workflowTaskRelationJson == null) {
                log.error("workflow task relation data is null.");
                throw new ServiceException(Status.DATA_IS_NOT_VALID, "null");
            }

            List<WorkflowTaskRelation> taskRelationList =
                    JSONUtils.toList(workflowTaskRelationJson, WorkflowTaskRelation.class);
            // Check whether the task node is normal
            List<TaskNode> taskNodes = processService.transformTask(taskRelationList, taskDefinitionLogsList);

            if (CollectionUtils.isEmpty(taskNodes)) {
                log.error("Task node data is empty.");
                throw new ServiceException(Status.WORKFLOW_DAG_IS_EMPTY);
            }

            // check has cycle
            if (graphHasCycle(taskNodes)) {
                log.error("workflow DAG has cycle.");
                throw new ServiceException(Status.WORKFLOW_NODE_HAS_CYCLE);
            }

            // check whether the workflow definition json is normal
            for (TaskNode taskNode : taskNodes) {
                if (!checkTaskParameters(taskNode.getType(), taskNode.getParams())) {
                    throw new ServiceException(Status.WORKFLOW_NODE_S_PARAMETER_INVALID, taskNode.getName());
                }

                // check extra params
                CheckUtils.checkOtherParams(taskNode.getExtras());
            }
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            log.error(Status.INTERNAL_SERVER_ERROR_ARGS.getMsg(), e);
            throw new ServiceException(Status.INTERNAL_SERVER_ERROR_ARGS, e.getMessage());
        }
    }

View on GitHub (pinned to 02eac45a1b)

Solutions

  1. Inspect each task's preTasks/postTasks in the workflow JSON and remove the dependency edge that closes the cycle.
  2. Redraw the workflow in the UI designer, which prevents connecting tasks in a cycle, and re-save.
  3. Write a client-side topological sort (Kahn's algorithm) check over taskRelations before calling the API.
  4. If imported, fix the source export and re-import.

Example fix

// before (cycle)
taskA.setPreTasks(Lists.newArrayList("taskB"));
taskB.setPreTasks(Lists.newArrayList("taskA"));
// after (acyclic)
taskA.setPreTasks(Collections.emptyList());
taskB.setPreTasks(Lists.newArrayList("taskA"));
Defensive patterns

Strategy: validation

Validate before calling

// Kahn's algorithm guard before calling the API
Map<String, List<String>> adj = buildAdjacency(taskRelations);
Deque<String> ready = nodesWithZeroIndegree(adj);
int removed = 0;
while (!ready.isEmpty()) { String n = ready.poll(); removed++; for (String m : adj.get(n)) if (--indeg(m) == 0) ready.add(m); }
if (removed < adj.size()) throw new IllegalStateException("workflow task relations contain a cycle");

Try / catch

try {
    saveWorkflow(json);
} catch (ServiceException e) {
    if (e.getCode() == Status.WORKFLOW_NODE_HAS_CYCLE.getCode()) {
        log.error("cyclic preTask references in workflow {}", name);
    } else throw e;
}

Prevention

When it happens

Trigger: Saving/updating/importing a workflow where task A depends on B and B (transitively) depends on A — i.e. the preTaskNode/postTaskNode relations in taskRelationJson form a directed cycle.

Common situations: Hand-editing task relations and creating circular pre-task references; importing workflows modified externally; copy-paste mistakes in taskRelationJson postTaskNodeList values; programmatic DAG generation bugs.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/dolphinscheduler@02eac45a1b (2026-09-06). Data as JSON: /api/errors/88ec1b5a03a8d85d. Report an issue: GitHub.