Flow Orchestration

Explicit flow orchestration for Spring Boot microservices

Pause, resume, route and trace execution flows across services — without hidden orchestration logic.

Spring Middleware Orchestrator is a lightweight orchestration engine for Spring Boot microservices. It models execution flows explicitly, with support for dynamic routing, pause and resume, timeout handling, and external event continuation.

Instead of scattering orchestration logic across services and consumers, the orchestrator makes flows declarative, inspectable, and extensible.

Why it exists

Make orchestration explicit instead of scattered

In many microservice systems, orchestration emerges implicitly: conditional branches spread across services, ad-hoc Kafka consumers triggering follow-up logic, duplicated timeout handling, and no clear representation of the flow itself. That makes the system harder to evolve, reason about, and debug.

Implicit orchestration becomes fragile

Logic ends up distributed across application services, event consumers, schedulers, and custom retry code, with no single place that represents the overall execution model.

Flows should be visible and traceable

The orchestrator makes execution explicit so teams can reason about transitions, pauses, resumes, timeouts, and final outcomes as first-class platform concepts.

Mental model

A deterministic state machine with optional dynamic transitions

A flow starts at a declared entry action, executes each step, resolves the next action, and continues until the flow finishes, pauses, errors, or times out. The execution model is simple, explicit, and designed for backend developers rather than BPM tooling specialists.

01

Start at firstAction

The engine begins at the flow entry point defined in the flow metadata.

02

Execute the action

Each action runs according to its action type and execution semantics.

03

Resolve the next step

The engine determines where the flow should go next through a built-in or custom resolver.

04

Stop, pause, or finish

The flow ends in a final state, pauses waiting for continuation, or redirects through timeout or error handling.

Core concepts

Flows, actions, and resolvers

The orchestrator revolves around three core concepts: the flow definition itself, the actions that compose it, and the resolver logic that determines transitions. This keeps the orchestration model explicit without making it heavyweight.

Flow

A flow defines the entry point, the actions involved, and the transition model between steps. It is the top-level description of the orchestration path.

{
  "flowId": { "value": "SIMPLE_FLOW" },
  "firstAction": "FIRST_ACTION",
  "actions": [ ... ]
}

Action

Each action has a name, a type, optional next-step logic, and optional timeout behavior. Actions are the execution units that model the flow path.

Resolver

A resolver decides which action comes next. The engine provides a built-in fixed resolver, and developers can implement their own for dynamic branching.

Action model

Action types reflect real backend execution patterns

The orchestrator is built around backend-oriented action types rather than generic workflow abstractions. This makes it easier to model enrichment steps, side-effect execution, and resumed continuations explicitly.

FUNCTION

FUNCTION actions transform input into output and represent pure computation or enrichment. They are typically used to prepare payloads, compute state, or derive context.

CONSUMER

CONSUMER actions perform side effects and do not return output. They are useful for Kafka publishing, external API calls, notifications, or any step that interacts with external systems.

RESUME

RESUME actions are entry points for continuation after a paused flow is resumed. They make asynchronous workflows explicit instead of hiding them in custom glue code.

Transition model

Built-in and custom resolvers

Every action execution must eventually resolve the next step in the flow. This transition model is one of the key extension points of the engine: teams can use fixed routing when the path is static, or implement custom resolvers when the flow depends on runtime conditions.

Fixed resolver

The built-in FIXED_NEXT_ACTION resolver transitions to a statically defined next action. It is the simplest and most direct orchestration model.

{
  "resolver": "FIXED_NEXT_ACTION",
  "parameters": {
    "nextAction": "SECOND_ACTION"
  }
}

Custom resolvers

Custom resolvers can choose the next step dynamically based on execution context, runtime values, or external input. That enables conditional routing, approval paths, probabilistic routing, and other dynamic flow patterns.

Conditional branching

Route to different actions depending on context or calculated state.

Probability-based routing

Support A/B-style routing or weighted path selection through custom resolver logic.

External decision model

Allow the next step to depend on information produced outside the immediate flow state.

Pause and resume

One of the most important capabilities of the engine

When a flow reaches a non-final CONSUMER action, the orchestrator can persist the execution state and stop execution, freeing threads and waiting for an external event to continue the flow later. This is especially useful for Kafka-driven continuation, callbacks, and long-lived asynchronous processes.

Pause behavior

If a non-final CONSUMER action is reached, the orchestrator persists the flow state, including payload and execution context, and moves the flow into a paused state.

Resume behavior

When an external system triggers continuation, the engine restores the persisted state from MongoDB and resumes execution from the configured RESUME action.

CREATE_CONTEXT SEND_CONTEXT RESUME_CONTEXT LAST_ACTION pause + persist state resume from persisted context
flowExecutor.resumeFlow(flowExecutionId, "RESUME_CONTEXT_ACTION", null);
Timeouts

Timeout-based redirection is part of the flow model

Flows can define timeout behavior explicitly on paused CONSUMER actions. If the flow is not resumed within the configured time, the engine redirects execution according to the timeout resolver. This makes expiration, fallback, and SLA-oriented behavior a first-class part of the orchestration model.

Timeout on paused steps

Timeout behavior applies naturally to paused CONSUMER steps, where the system is waiting for an external continuation.

Resolver-driven outcome

Timeout behavior is not hardcoded. It redirects through a resolver, so timeout can end the flow, move into an error branch, or continue through a fallback path.

{
  "actionName": "WAIT_FOR_PAYMENT",
  "actionType": "CONSUMER",
  "timeout": {
    "seconds": 30,
    "resolver": "FIXED_NEXT_ACTION",
    "parameters": {
      "nextAction": "PAYMENT_TIMEOUT_ERROR_ACTION"
    }
  }
}
Example flows

Different patterns already validated

The orchestrator already supports simple linear flows, chained execution, dynamic branching, pause/resume continuation, and timeout-based completion or failure handling.

Simple flow

FIRST_ACTION → SECOND_ACTION

Chained flow

FIRST → SECOND → THIRD

Resolver flow

FIRST → resolver → FIRST_PROB / SECOND_PROB

Context flow

CREATE → SEND (pause) → RESUME → LAST

Timeout to END

CREATE → SEND (pause + timeout) → END

Timeout to ERROR

CREATE → SEND (pause + timeout) → ERROR

Execution traceability

Each flow produces an execution trail

Every flow execution yields structured runtime information: execution identifiers, timestamps, execution status, ordered action executions, payload, and context. This makes orchestration behavior inspectable and testable without requiring a separate workflow platform.

flowExecutionId

Each run is tracked with its own execution identifier.

requestId

A request correlation identifier can be carried across execution and continuation.

Ordered actions

Each action records order, status, error information, and execution timestamp.

Execution status

Flows expose states such as executed, suspended, or error depending on the runtime path.

Positioning

What this is and what it is not

Spring Middleware Orchestrator is deliberately opinionated. It is not trying to become a heavyweight BPM suite or a black-box workflow server. It is a developer-friendly orchestration engine meant to fit naturally into Spring Boot microservice systems.

This is not

  • A BPM engine
  • A heavy workflow platform
  • A black-box orchestration runtime

This is

  • Explicit
  • Minimal
  • Composable
  • Developer-friendly
Validated capabilities

Already proving real orchestration behavior

The current implementation already validates fixed transitions, multi-step chaining, custom resolver execution, pause and resume with persisted context, Kafka-driven continuation, timeout redirection, and error propagation. This is already a serious orchestration engine, not a toy example.

Fixed transitions

Simple deterministic flows are supported directly through built-in resolvers.

Pause and resume

Execution state can be persisted and restored for asynchronous continuation.

Timeout redirection

Paused flows can redirect automatically to END or ERROR style branches.

Custom resolver execution

Dynamic next-step routing already works as part of the execution engine.

Kafka-driven continuation

External messaging can drive resumed execution paths naturally.

Error propagation

Execution failures are captured explicitly in the flow execution trail.

Modules

Structured as engine, infrastructure, and demo

The orchestrator is organized as modular components so the core engine stays separated from persistence, messaging integration, and example applications.

orchestrator-core

Contains the engine itself, including the flow executor, resolvers, execution context model, and flow runtime abstractions.

orchestrator-infra

Contains infrastructure bindings such as MongoDB persistence, Kafka integrations, scheduling support, and runtime infrastructure wiring.

orchestrator-demo

A reference Spring Boot application that demonstrates orchestration patterns and runtime behavior.

examples

JSON flow definitions illustrating simple flows, chained flows, resolvers, pause/resume, and timeout-driven execution paths.

Getting started

Add the orchestrator modules to your Spring Boot application

The orchestrator is designed to fit naturally into Spring Boot applications. Add the required modules, implement your actions, define the flow, and trigger execution through the engine.

Dependencies

<dependency>
    <groupId>io.github.spring-middleware</groupId>
    <artifactId>orchestrator-core</artifactId>
    <version>1.0.0</version>
</dependency>

<dependency>
    <groupId>io.github.spring-middleware</groupId>
    <artifactId>orchestrator-infra</artifactId>
    <version>1.0.0</version>
</dependency>

Action implementation

@Component
public class CreateOrderAction implements FunctionAction<OrderRequest, OrderContext> {

    @Override
    public OrderContext execute(OrderRequest input, ExecutionContext context) {
        return new OrderContext(input.getId(), "CREATED");
    }
}

Flow definition

{
  "flowId": { "value": "ORDER_CREATION_FLOW" },
  "firstAction": "CREATE_ORDER",
  "actions": [
    {
      "actionName": "CREATE_ORDER",
      "actionType": "FUNCTION"
    }
  ]
}

Flow trigger

@Service
public class OrderService {

    private final FlowExecutor flowExecutor;

    public OrderService(FlowExecutor flowExecutor) {
        this.flowExecutor = flowExecutor;
    }

    public void startOrder(OrderRequest request) {
        flowExecutor.startFlow("ORDER_CREATION_FLOW", request);
    }
}
Next steps

Explore how orchestration fits into the broader platform

The orchestrator complements Spring Middleware’s broader platform model around messaging, persistence, registry-driven architecture, and explicit microservice infrastructure.