EasyWorkflow for LangChain4j provides a fluent DSL for building complex agentic workflows on top of the LangChain4j Agentic framework. It removes boilerplate and makes it simple to express AI workflows in a clear, readable way.
On top of clean workflow design, EasyWorkflow gives you other powerful tools:
- Workflow Debugger – Step inside your workflows with full visibility into context and agent execution results. Set breakpoints on key events, like agent input or output, to watch your workflow come alive in real time.
- Visual Flow Diagrams – Instantly generate flowcharts for your workflows, making it easy to debug, inspect invocation results and progress, document, and illustrate agent logic at a glance.
With EasyWorkflow, you can define workflows that include sequences of agents, conditional branches, parallel execution, agent groups, and loops, combining flexibility with elegance.
NovelCreator novelCreator = EasyWorkflow.builder(NovelCreator.class)
.chatModel(BASE_MODEL)
.agent(CreativeWriter.class)
.agent(AudienceEditor.class)
.repeat(agenticScope -> agenticScope.readState("score", 0.0) >= 0.8)
.agent(StyleScorer.class)
.breakpoint("Score: {{score}}")
.agent(StyleEditor.class)
.end()
.output(OutputComposers.asBean(Novel.class))
.build();
Novel novel = novelCreator.createNovel("dragons and wizards", "infants", "fantasy");
System.out.println(novel);This project is currently an incubator and is expected to be merged into the core LangChain4j framework in the future. It is evolving rapidly and may take new directions. We encourage you to explore it, try it out, and share your ideas, feedback, and suggestions with us.
- Fluent API: A simple and intuitive DSL-style API for defining complex agentic workflows.
- Workflow Debugger: Debug agentic workflows and examine the results of their execution.
- Sequential Execution: Define a sequence of agents that will be executed one after another.
- Conditional Execution: Execute agents based on a condition.
- Parallel Execution: Execute agents in parallel and compose their outputs.
- Agent Grouping: Group agents and supervise their execution.
- Loops: Repeat a sequence of agents till a condition evaluates to
true. - Logging: Simple switches to turn ON agents' input and output logging.
- Output Composers: Lots of ready-to-use output composers.
To add EasyWorkflow to your build system, you can use the following Maven dependency:
<dependency>
<groupId>io.github.gregory-ledenev</groupId>
<artifactId>langchain4j-easyworkflow</artifactId>
<version>1.0.0</version>
</dependency>to get JavaDoc for it:
<dependency>
<groupId>io.github.gregory-ledenev</groupId>
<artifactId>langchain4j-easyworkflow</artifactId>
<version>1.0.0</version>
<classifier>javadoc</classifier>
</dependency>The EasyWorkflow is the main entry point for creating workflows. Here’s how to use it for common tasks. To start you
can use EasyWorkflow.builder(Class<?> agentClass) method to get a builder object and provide the main agentic
interface. Then you can configure it, add agents and finally use the build as a terminal operation to create a
configured workflow.
You may explore Building AI Workflows with EasyWorkflow for LangChain4j tutorial for a quick start.
EasyWorkflow allows you to export a workflow to JSON format for documentation or illustration purposes using the
AgentWorkflowBuilder.toJson() method. In addition, you can generate an AI-powered summary of your workflow by calling
AgentWorkflowBuilder.generateAISummary().
Before adding agents, you need to configure the workflow. At a minimum, you must provide a ChatModel. You can also
provide a ChatMemory instance to maintain conversation history and specify some other properties.
// Import your chat model, e.g., OpenAiChatModel
// Import a chat memory, e.g., MessageWindowChatMemory
ExpertRouterAgent expertRouterAgent = EasyWorkflow.builder(ExpertRouterAgent.class)
.chatModel(chatModel) // Mandatory: The chat model for the agents
.chatMemory(chatMemory) // Optional: Shared memory for the agents
// ... add agents and control flow here
.build();It is possible to set up input and output logging by using logInput(true) and logOutput(true) methods.
BeanListEveningPlannerAgent agent = EasyWorkflow.builder(BeanListEveningPlannerAgent.class)
.chatModel(BASE_MODEL)
.logInput(true)
.logOutput(true)
.doParallel()
.agent(FoodExpert.class)
.agent(MovieExpert.class)
.end()
.build();You can add agents to the workflow to be executed sequentially. You can add an agent by its class or by providing an
already-created instance. An agent can be defined either by an interface or a class, having a single method, annotated
with an @Agent annotation.
See more: Agents in LangChain4j
NovelCreator novelCreator = EasyWorkflow.builder(NovelCreator.class)
.chatModel(BASE_MODEL)
// Add agents by their class
.agent(CreativeWriter.class)
.agent(AudienceEditor.class)
// You can also add a pre-configured agent instance
// .agent(new MyCustomAgent())
.build();
public interface CreativeWriter {
@UserMessage('''
You are a creative writer. Generate a draft of a story no more than 3 sentences long around the
given topic. Return only the story and nothing else. The topic is {{topic}}.
''')
@Agent(value = "Generates a story based on the given topic", outputName = "story")
String generateStory(@V("topic") String topic);
}If you need to access a complete set of properties allowed by agent's builder — create an agent, providing a customizer lambda function:
SupervisorAgent supervisorAgent1 = EasyWorkflow.builder(SupervisorAgent.class)
.chatModel(BASE_MODEL)
.doAsGroup()
.agent(WithdrawAgent.class, builder -> builder.agentBuilderCustomizer(bankTool))
.agent(CreditAgent.class, builder -> builder.agentBuilderCustomizer(bankTool))
.agent(ExchangeAgent.class, builder -> builder.agentBuilderCustomizer(new ExchangeTool()))
.agent(humanInTheLoop)
.end()
.build();For more complex workflows, you can use control flow statements like ifThen, dowWhen/match, repeat, doParallel, and group. Each
of these statements opens a block that must be closed with the end() method.
Here's an example combining a conditional and a loop:
ExpertRouterAgent expertRouterAgent = EasyWorkflow.builder(ExpertRouterAgent.class)
.chatModel(BASE_MODEL)
.agent(CategoryRouter.class)
// Execute a block of agents only if a condition is met
.ifThen(scope -> scope.readState("category", "UNKNOWN").equals("LEGAL"))
.agent(LegalExpert.class)
// You can nest control flow
.repeat(2, scope -> scope.readState("isClear", false) == true)
.agent(LegalClarifier.class)
.end() // Closes the 'repeat' block
.end() // Closes the 'ifThen' block
.build();You can find more detailed examples in the following sections.
Note: it is not possible to extract conditions and expressions from corresponding lambda functions for illustration or diagramming purposes. So to associate textual representation of conditions and expression with lanbda functions use
EasyWorkflow.condition(lambda, String)- for conditions used inifThenandrepeatEasyWorkflow.expression(lambda, String))- for expressions used indoWhen
The WorkflowDebugger provides debugging functionality for agentic workflows. It allows you to:
- Examine the workflow context, including the results of agent executions.
- Define and handle breakpoints for events like agent input and output.
To use the debugger, create an instance of WorkflowDebugger and attach it to a workflow using the builder's
debugger(...) method:
WorkflowDebugger debugger = new WorkflowDebugger();
NovelCreator novelCreator = EasyWorkflow.builder(NovelCreator.class)
.chatModel(BASE_MODEL)
.debugger(debugger)
.agent(CreativeWriter.class)
.agent(AudienceEditor.class)
.repeat(5, agenticScope -> agenticScope.readState("score", 0.0) < 0.8)
.agent(StyleScorer.class)
.agent(StyleEditor.class)
.end()
.build();
String story = novelCreator.createNovel("dragons and wizards", "infants", "fantasy");The WorkflowDebugger captures the AgenticScope, which can be accessed with getAgenticScope() to examine the
workflow's context and data. Note that the scope is only available after the workflow has started.
You can check if a workflow is running with isStarted(), get its input parameters with getWorkflowInput(), and
retrieve the final result with getWorkflowResult(). If a workflow failed - you can obtain a failure via the getWorkflowFailure() method. The failure may be complex, containing lots of nested exceptions, so to get the cause use the getFailureCauseException() method.
The debugger also tracks and caches all agent invocations. You can access this history with
getAgentInvocationTraceEntries() to analyze the workflow process. Each entry contains details about the agent, its
input, and its output.
For a human-readable summary of the workflow execution, you can use the WorkflowDebugger.toString(true) method:
↓ IN > "audience": infants
↓ IN > "topic": dragons and wizards
↓ IN > "style": fantasy
-----------------------
↓ IN: UserMessage { name = null contents = [TextContent { text = "You are a creative writer..
1. ▷︎ CreativeWriter
↓ OUT > "story": In the mystical realm of Aethoria, where dragons soared the skies and wizards...
-----------------------
↓ IN: UserMessage { name = null contents = [TextContent { text = "You are a professional editor...
2. ▷︎ AudienceEditor
↓ OUT > "story": In a magical land, a little friend named Eryndor made a best buddy, a big, friendly...
-----------------------
...
-----------------------
◼ RESULT: Novel[story=In the enchanted realm of Aethoria, a young companion named Eryndor befriended...
The debugger allows you to define breakpoints that trigger custom actions. These actions can be used for logging, custom processing, or to pause execution for IDE debugging.
The available breakpoint types are defined in Breakpoint.Type:
- AGENT_INPUT: Triggers when an agent receives input.
- AGENT_OUTPUT: Triggers when an agent produces output.
- SESSION_STARTED: Triggers when a workflow session starts.
- SESSION_STOPPED: Triggers when a workflow session stops.
- SESSION_FAILED: Triggers when a workflow session fails.
- LINE: A procedural breakpoint that triggers when reached in the workflow definition.
For most breakpoint types, you can define them using Breakpoint.builder() and add them with
WorkflowDebugger.addBreakpoint().
The following code shows how to add breakpoints to print the input for StyleScorer.class and to print the score
output when it's greater than or equal to 0.2:
WorkflowDebugger debugger = new WorkflowDebugger();
debugger.addBreakpoint(Breakpoint.builder((b, ctx) -> {
System.out.println("INPUT: " + ctx.getOrDefault(WorkflowDebugger.KEY_INPUT, null)");" +
"})
.agentClasses(StyleScorer.class)
.type(WorkflowDebugger.Breakpoint.Type.INPUT)
.build());
debugger.addBreakpoint(Breakpoint.builder((b, ctx) -> {
System.out.println("SCORE: " + ctx.getOrDefault("score", 0.0)");
})
.outputNames("score")
.condition(ctx -> (double) ctx.getOrDefault("score", 0.0) >= 0.2)
.build());LINE breakpoints are added directly into the workflow definition using the breakpoint() method on the workflow
builder:
NovelCreator novelCreator = EasyWorkflow.builder(NovelCreator.class)
.chatModel(BASE_MODEL)
.workflowDebugger(workflowDebugger)
.agent(CreativeWriter.class)
.agent(AudienceEditor.class)
.repeat(agenticScope -> agenticScope.readState("score", 0.0) < 0.8)
.agent(StyleScorer.class)
.breakpoint("SCORE (INLINE): {{score}}", ctx -> (double) ctx.getOrDefault("score", 0.0) >= 0.0)
.agent(StyleEditor.class)
.end()
.output(OutputComposers.asBean(Novel.class))
.build();The BreakpointActions class provides several methods that allow creation of common utility breakpoint actions:
log(...)- action that logs a message using a prompt template.toggleBreakpoints(...)- action that toggles the enabled state of other specified breakpoints.toHtmlFile(...)- action that generates an HTML file representing the workflow execution.
It is possible to implement and use non-AI agents that are coded-manually and produce results without involving AI
functionality. Unfortunately, such agents can't be tracked automatically by WofkflowDebugger so they should be adopted
to support debugging. To support debugging, each agent must:
- Implement
WorkflowDebuggerSupportinterface and save provided instances ofWofkflowDebugger. - Call an
inputReceived(...)method to notify debugger about input received. - Call the
outputProduced(...)method to notify debugger about output produced.
public static class QualityScorer implements WorkflowDebuggerSupport {
private WorkflowDebugger workflowDebugger;
@Agent(outputName = "quality")
public double scoreStyle(@V("story") String story) {
if (workflowDebugger != null) {
inputReceived(story);
outputProduced(0.74);
}
return 0.74;
}
@Override
public WorkflowDebugger getWorkflowDebugger() { return workflowDebugger; }
@Override
public void setWorkflowDebugger(WorkflowDebugger workflowDebugger) { this.workflowDebugger = workflowDebugger; }
}EasyWorkflow provides functionality to generate visual flowchart diagrams of agentic workflows, accompanied by the workflow results, as HTML files. These diagrams are invaluable for debugging, illustration, and documentation purposes. All agent nodes that have been invoked are highlighted with bold borders and are clickable. Clicking a node opens the Inspector, allowing you to preview the agent’s input and output.
You can create these diagrams using the WorkflowDebugger.toHtmlFile(...) method to save the diagram directly to an
HTML file, or the WorkflowDebugger.toHtml() method to obtain the HTML content as a string. Generating the HTML file at
breakpoints enables visualization of workflow progress. Some IDEs, such as IntelliJ IDEA, offer auto-refreshable HTML
views, so opening the diagram in such a view lets you monitor progress and data in real time.
You may check a sample Expert Agentic Workflow Diagram by opening it in the browser.
There is a special WorkflowExpert agent that provides an ability to chat with the debugger about workflow, its structure and properties, and about its execution results. A WorkflowExpert agent can be obtained via the WorkflowExpertSupport.getWorkflowExpert(...) method. There's also a convenience method WorkflowExpertSupport.play(...) that starts a playground session with a WorkflowExpert.
EasyWorkflow provides an interactive playground for experimenting with agents (workflows) through a familiar chat-style interface. You can type a query, send it to an agent, receive a response, and repeat the process as often as needed to test and refine your agents.
A playground is created with Playground.createPlayground(...), where you specify the agent’s class and the playground type. To start a chat session, call Playground.play(), passing the agent and optionally an initial user message.
Playground playground = Playground.createPlayground(NovelCreator.class, Playground.Type.GUI);
playground.play(novelCreator, Map.of(
"topic", "dragons and wizards",
"audience", "infants",
"style", "fantasy"
));You may specify instructions for Playground how to present and edit certain agent method parameters using !PlaygroundParam annotation.
@PlaygroundParam(description = "Story style",
editorType = FormEditorType.EditableDropdown,
editorChoices = {"comedy", "horror", "fantasy", "romance"})
@V("style")
String style);If your workflow requires user input during execution, use an agent created via the HumanInTheLoopAgents.playgroundAgent(...). This ensures that the input will be collected automatically in a way appropriate for the selected playground type.
There are two available playground types:
The console playground lets you chat with an agent directly via the terminal. If the agent expects multiple parameters, you can pass them as a semicolon-separated string.
PLAYGROUND (type your questions or arguments, or 'exit' to quit)
To enter specific arguments, delimit them with a semicolon.
Arguments:
- topic (Type: String)
- audience (Type: String)
- style (Type: String)
> {topic=dragons and wizards; audience=infants; style=fantasy}
[thinking...]
Answer: Novel[story=In the enchanted realm of Aethoria...
>
The GUI playground offers a modern and convenient interface to allow testing the workflows using the Chat interface, inspect their structure, execution results, and summary.
By default, GUI Playground shows a simple Chat UI. To show GUI Playground in advanced mode with ability to see structure and execution results — specify a Workflow Debugger.
Playground playground = Playground.createPlayground(NovelCreator.class,
Playground.Type.GUI,
workflowDebugger);If an agent requires multiple arguments, they are presented in a form layout for easier input. To customize form - use @PlaygroundParam annotation, where you can specify how each agent's method parameter should be rendered, its description, value choices etc.
public interface NovelCreator extends AgenticScopeOwner {
@Agent(outputName = "story")
Novel createNovel(
@PlaygroundParam(editorType = FormEditorType.Note, description = "Story topic")
@V("topic")
String topic,
@PlaygroundParam(description = "Story audience", editorType = FormEditorType.EditableDropdown, editorChoices = {"infants", "children", "teenagers", "adults"})
@V("audience")
String audience,
@PlaygroundParam(description = "Story style", editorType = FormEditorType.EditableDropdown, editorChoices = {"comedy", "horror", "fantasy", "romance"})
@V("style")
String style);
}You may check the runnable GUI playground at SampleConditionalAgentsPlayground.java
The following example shows how to create a sequential workflow with a repeatable block of agents. You may check the Sequential Workflow and Loop Workflow for complete samples description or check the runnable test at SampleSequentialAndRepeatableAgents.java
NovelCreator novelCreator = EasyWorkflow.builder(NovelCreator.class)
.chatModel(BASE_MODEL)
.agent(CreativeWriter.class)
.agent(AudienceEditor.class)
.repeat(5, agenticScope -> agenticScope.readState("score", 0.0) >= 0.8)
.agent(StyleScorer.class)
.agent(StyleEditor.class)
.end()
.build();
String story = novelCreator.createNovel("dragons and wizards", "infants", "fantasy");The following example shows how to create a workflow with conditional execution of agents using a set of ifThen
statements. You may check the Conditional Workflow
for complete samples description or check the runnable test
at SampleConditionalAgents.java
ExpertRouterAgent expertRouterAgent = EasyWorkflow.builder(ExpertRouterAgent.class)
.chatModel(BASE_MODEL)
.chatMemory(chatMemory)
.agent(CategoryRouter.class)
.ifThen(agenticScope -> agenticScope.readState("category", RequestCategory.UNKNOWN) == RequestCategory.MEDICAL)
.agent(MedicalExpert.class).end()
.ifThen(agenticScope -> agenticScope.readState("category", RequestCategory.UNKNOWN) == RequestCategory.LEGAL)
.agent(LegalExpert.class).end()
.ifThen(agenticScope -> agenticScope.readState("category", RequestCategory.UNKNOWN) == RequestCategory.TECHNICAL)
.agent(TechnicalExpert.class).end()
.build();
expertRouterAgent.ask("Should I sue my neighbor who caused this damage?");The following example shows how to create a workflow with conditional execution of agents using a doWhen statement.
You may check the Conditional Workflow for complete
samples description or check the runnable test
at SampleSwitchAgents.java
ExpertRouterAgent expertRouterAgent = EasyWorkflow.builder(ExpertRouterAgent.class)
.chatModel(BASE_MODEL)
.chatMemory(chatMemory)
.agent(CategoryRouter.class)
.doWhen(agenticScope -> agenticScope.readState("category", RequestCategory.UNKNOWN))
.match(RequestCategory.MEDICAL)
.agent(MedicalExpert.class)
.end()
.match(RequestCategory.LEGAL)
.agent(LegalExpert.class)
.end()
.match(RequestCategory.TECHNICAL)
.agent(TechnicalExpert.class)
.end()
.end()
.build();
expertRouterAgent.ask("How to setup a VPN?");The following example shows how to create a workflow with parallel execution of agents. You may check the Parallel Workflow for complete samples description or check the runnable test at SampleParallelAgents.java
Function<AgenticScope, Object> resultFunction = agenticScope -> {
List<String> movies = agenticScope.readState("movies", List.of());
List<String> meals = agenticScope.readState("meals", List.of());
List<EveningPlan> moviesAndMeals = new ArrayList<>();
for (int i = 0; i < movies.size(); i++) {
if (i >= meals.size()) {
break;
}
moviesAndMeals.add(new EveningPlan(movies.get(i), meals.get(i)));
}
return moviesAndMeals;
};
EveningPlannerAgent eveningPlannerAgent = EasyWorkflow.builder(EveningPlannerAgent.class)
.chatModel(BASE_MODEL)
.doParallel(resultFunction)
.agent(FoodExpert.class)
.agent(MovieExpert.class)
.end()
.build();
eveningPlannerAgent.plan("happy");The following example shows how to create a workflow with a supervised group of agents that form a pure agentic AI. You may check the Pure Agentic AI for complete samples description or check the runnable test at SampleSupervisedAgents.java
SupervisorAgent supervisorAgent1 = EasyWorkflow.builder(SupervisorAgent.class)
.chatModel(BASE_MODEL)
.doAsGroup()
.agent(WithdrawAgent.class, builder -> builder.agentBuilderCustomizer(bankTool))
.agent(CreditAgent.class, builder -> builder.agentBuilderCustomizer(bankTool))
.agent(ExchangeAgent.class, builder -> builder.agentBuilderCustomizer(new ExchangeTool()))
.agent(humanInTheLoop)
.end()
.build();The EasyWorkflow for LangChain4j is licensed under the terms of the MIT License.

