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.
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.
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.
Logic ends up distributed across application services, event consumers, schedulers, and custom retry code, with no single place that represents the overall execution model.
The orchestrator makes execution explicit so teams can reason about transitions, pauses, resumes, timeouts, and final outcomes as first-class platform concepts.
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.
The engine begins at the flow entry point defined in the flow metadata.
Each action runs according to its action type and execution semantics.
The engine determines where the flow should go next through a built-in or custom resolver.
The flow ends in a final state, pauses waiting for continuation, or redirects through timeout or error handling.
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.
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": [ ... ]
}
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.
A resolver decides which action comes next. The engine provides a built-in fixed resolver, and developers can implement their own for dynamic branching.
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 actions transform input into output and represent pure computation or enrichment. They are typically used to prepare payloads, compute state, or derive context.
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 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.
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.
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 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.
Route to different actions depending on context or calculated state.
Support A/B-style routing or weighted path selection through custom resolver logic.
Allow the next step to depend on information produced outside the immediate flow state.
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.
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.
When an external system triggers continuation, the engine restores the persisted state from MongoDB and resumes execution from the configured RESUME action.
flowExecutor.resumeFlow(flowExecutionId, "RESUME_CONTEXT_ACTION", null);
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 behavior applies naturally to paused CONSUMER steps, where the system is waiting for an external continuation.
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"
}
}
}
The orchestrator already supports simple linear flows, chained execution, dynamic branching, pause/resume continuation, and timeout-based completion or failure handling.
FIRST_ACTION → SECOND_ACTION
FIRST → SECOND → THIRD
FIRST → resolver → FIRST_PROB / SECOND_PROB
CREATE → SEND (pause) → RESUME → LAST
CREATE → SEND (pause + timeout) → END
CREATE → SEND (pause + timeout) → ERROR
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.
Each run is tracked with its own execution identifier.
A request correlation identifier can be carried across execution and continuation.
Each action records order, status, error information, and execution timestamp.
Flows expose states such as executed, suspended, or error depending on the runtime path.
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.
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.
Simple deterministic flows are supported directly through built-in resolvers.
Execution state can be persisted and restored for asynchronous continuation.
Paused flows can redirect automatically to END or ERROR style branches.
Dynamic next-step routing already works as part of the execution engine.
External messaging can drive resumed execution paths naturally.
Execution failures are captured explicitly in the flow execution trail.
The orchestrator is organized as modular components so the core engine stays separated from persistence, messaging integration, and example applications.
Contains the engine itself, including the flow executor, resolvers, execution context model, and flow runtime abstractions.
Contains infrastructure bindings such as MongoDB persistence, Kafka integrations, scheduling support, and runtime infrastructure wiring.
A reference Spring Boot application that demonstrates orchestration patterns and runtime behavior.
JSON flow definitions illustrating simple flows, chained flows, resolvers, pause/resume, and timeout-driven execution paths.
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.
<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>
@Component
public class CreateOrderAction implements FunctionAction<OrderRequest, OrderContext> {
@Override
public OrderContext execute(OrderRequest input, ExecutionContext context) {
return new OrderContext(input.getId(), "CREATED");
}
}
{
"flowId": { "value": "ORDER_CREATION_FLOW" },
"firstAction": "CREATE_ORDER",
"actions": [
{
"actionName": "CREATE_ORDER",
"actionType": "FUNCTION"
}
]
}
@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);
}
}
The orchestrator complements Spring Middleware’s broader platform model around messaging, persistence, registry-driven architecture, and explicit microservice infrastructure.