Spring AI Workflow Automation — Build Multi-Step AI Pipelines
Complex AI tasks require multiple sequential AI calls where the output of one step feeds the next. Workflow automation patterns let you build structured pipelines with validation gates, human-in-the-loop approval, parallel branches, and conditional routing — all orchestrated with Spring AI and Spring State Machine or simple service chains.
Workflow Types
Sequential Pipeline:
Input → Step1 → Step2 → Step3 → Output
Example: Document → Extract → Validate → Transform → Save
Conditional Branching:
Input → Classify → [if "complaint"] → EscalationFlow
→ [if "question"] → AnswerFlow
→ [if "spam"] → RejectFlow
Human-in-the-Loop:
AI Draft → Human Review → [approve] → Publish
→ [reject] → AI Revision → Human Review
Parallel Fan-Out:
Document → [concurrent] → Translate EN
→ Translate FR
→ Summarize
→ Extract Keywords
→ Merge Results → Store
Sequential Pipeline — Content Creation Workflow
@Service
public class ContentCreationPipeline {
private final ChatClient chatClient;
public ContentCreationPipeline(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public PublishedContent createBlogPost(String topic, String targetAudience) {
System.out.println("Step 1: Research topic");
String outline = step1_createOutline(topic, targetAudience);
System.out.println("Step 2: Write draft");
String draft = step2_writeDraft(topic, outline);
System.out.println("Step 3: Review and improve");
String improved = step3_reviewAndImprove(draft);
System.out.println("Step 4: SEO optimization");
SeoContent seoContent = step4_seoOptimize(improved, topic);
System.out.println("Step 5: Final validation");
boolean valid = step5_validate(seoContent);
if (!valid) {
throw new ContentQualityException("Content failed quality validation");
}
return new PublishedContent(topic, seoContent, LocalDateTime.now());
}
private String step1_createOutline(String topic, String audience) {
return chatClient.prompt()
.user("Create a detailed outline for a blog post about '%s' for %s. 5-7 sections."
.formatted(topic, audience))
.call().content();
}
private String step2_writeDraft(String topic, String outline) {
return chatClient.prompt()
.system("You are an expert technical writer. Write engaging, accurate content.")
.user("Write a complete blog post about '%s' following this outline:\n%s"
.formatted(topic, outline))
.call().content();
}
private String step3_reviewAndImprove(String draft) {
return chatClient.prompt()
.system("You are an editor. Improve clarity, flow, and technical accuracy.")
.user("Review and improve this blog post:\n" + draft)
.call().content();
}
private SeoContent step4_seoOptimize(String content, String topic) {
return chatClient.prompt()
.user("""
Create SEO-optimized metadata for this content about '%s':
%s
Extract: title, metaDescription (155 chars), keywords (5-10), content
""".formatted(topic, content.substring(0, Math.min(1000, content.length()))))
.call()
.entity(SeoContent.class);
}
private boolean step5_validate(SeoContent content) {
String validation = chatClient.prompt()
.user("""
Quality check this content:
Title: %s
Meta: %s
Word count: ~%d
Does it meet quality standards?
Respond with JSON: {"passed": true/false, "reason": "..."}
""".formatted(content.title(), content.metaDescription(),
content.body().split("\\s+").length))
.call().content();
return validation.contains("\"passed\": true") || validation.contains("\"passed\":true");
}
}
record SeoContent(String title, String metaDescription, List<String> keywords, String body) {}
record PublishedContent(String topic, SeoContent content, LocalDateTime createdAt) {}
Conditional Branching Workflow
@Service
public class CustomerRequestRouter {
private final ChatClient chatClient;
private final ComplaintService complaintService;
private final SupportService supportService;
public String routeRequest(String customerMessage) {
// Step 1: Classify
String classification = chatClient.prompt()
.user("""
Classify this customer message into one category:
complaint | question | feedback | spam
Message: %s
Output ONLY the category word.
""".formatted(customerMessage))
.call().content().trim().toLowerCase();
System.out.println("Classified as: " + classification);
// Step 2: Route to appropriate handler
return switch (classification) {
case "complaint" -> complaintService.handle(customerMessage);
case "question" -> supportService.answer(customerMessage);
case "feedback" -> "Thank you for your feedback. We'll review it.";
case "spam" -> "Message rejected.";
default -> supportService.answer(customerMessage); // safe default
};
}
}
Parallel Fan-Out Pipeline
@Service
public class DocumentTranslationPipeline {
private final ChatClient chatClient;
private final ExecutorService executor =
Executors.newFixedThreadPool(4); // parallel AI calls
public TranslatedDocument translateAll(String documentText) throws Exception {
// Launch 4 translations in parallel
Future<String> enFuture = executor.submit(() -> translate(documentText, "English"));
Future<String> frFuture = executor.submit(() -> translate(documentText, "French"));
Future<String> deFuture = executor.submit(() -> translate(documentText, "German"));
Future<String> esFuture = executor.submit(() -> translate(documentText, "Spanish"));
// Also run summary in parallel
Future<String> summaryFuture = executor.submit(() -> summarize(documentText));
// Collect all results (blocks until all complete)
return new TranslatedDocument(
enFuture.get(),
frFuture.get(),
deFuture.get(),
esFuture.get(),
summaryFuture.get()
);
}
private String translate(String text, String language) {
return chatClient.prompt()
.system("Translate to " + language + ". Keep technical terms accurate.")
.user(text)
.call().content();
}
private String summarize(String text) {
return chatClient.prompt()
.user("Summarize in 3 bullet points:\n" + text)
.call().content();
}
}
record TranslatedDocument(String en, String fr, String de, String es, String summary) {}
Output
// ContentCreationPipeline.createBlogPost("Spring AI RAG", "Java developers")
Step 1: Research topic
Step 2: Write draft
Step 3: Review and improve
Step 4: SEO optimization
Step 5: Final validation
PublishedContent[
topic="Spring AI RAG",
content=SeoContent[
title="Spring AI RAG Tutorial: Build Intelligent Chatbots in Java",
metaDescription="Learn how to build RAG-powered chatbots with Spring AI...",
keywords=["spring ai", "rag", "java", "chatbot", "vector store"],
body="... 1200 word blog post ..."
],
createdAt=2026-06-12T10:30:00
]
// DocumentTranslationPipeline — all 4 translations run in parallel
// Time: max(translation time) ≈ 5s instead of 4 × 5s = 20s
Key Points
- Break complex AI tasks into named steps (step1, step2...) — it makes debugging easy when one step produces bad output
- Use
ExecutorServicewithnewFixedThreadPoolfor parallel AI calls — keep the pool size under your API rate limit - Add a quality gate step at the end of every pipeline — it catches bad AI output before it reaches production
- Log the output of each pipeline step to help diagnose where quality degraded when the final output is wrong
- For long-running workflows, persist step outputs to a database so you can resume from the last completed step if the process crashes mid-pipeline
Comments