Spring AI Prompt Templates — Dynamic Prompts with Variable Substitution
Hard-coding prompt strings inside Java methods makes them difficult to maintain and reuse. Spring AI's PromptTemplate class solves this by letting you define prompts with named placeholders and fill them in at runtime — similar to how Thymeleaf or FreeMarker work for HTML templates.
This tutorial covers PromptTemplate, loading templates from files, multi-variable substitution, and using template prompts with role-based messages.
Basic PromptTemplate
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.chat.prompt.Prompt;
@Service
public class TutorialService {
private final ChatClient chatClient;
public TutorialService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String generateTutorial(String topic, String level) {
PromptTemplate template = new PromptTemplate("""
Write a {level} tutorial about {topic} for Java developers.
Include a working code example and explain the output.
""");
Prompt prompt = template.create(Map.of(
"topic", topic,
"level", level
));
return chatClient.prompt(prompt).call().content();
}
}
Test the Service
// Call:
tutorialService.generateTutorial("Spring AI RAG", "beginner");
// Output:
// Beginner's Guide to Spring AI RAG
// RAG (Retrieval Augmented Generation) is a pattern where you...
// [complete tutorial with code example follows]
Load Templates from Classpath Files
For complex prompts, store them as .st (StringTemplate) files in src/main/resources/prompts/.
File: src/main/resources/prompts/code-review.st
You are a senior Java engineer performing a code review.
Review the following {language} code for:
- Correctness and bugs
- Performance issues
- Security vulnerabilities
- Best practice violations
Code to review:
```{language}
{code}
```
Provide specific line-by-line feedback where applicable.
import org.springframework.core.io.ClassPathResource;
@Service
public class CodeReviewService {
private final ChatClient chatClient;
public CodeReviewService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String reviewCode(String language, String code) {
PromptTemplate template = new PromptTemplate(
new ClassPathResource("prompts/code-review.st")
);
Prompt prompt = template.create(Map.of(
"language", language,
"code", code
));
return chatClient.prompt(prompt).call().content();
}
}
System + User Template Combination
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.prompt.Prompt;
@Service
public class DocumentService {
private final ChatClient chatClient;
public DocumentService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String summarize(String document, String language) {
PromptTemplate systemTemplate = new PromptTemplate(
"You are a technical writer. Always respond in {language}."
);
PromptTemplate userTemplate = new PromptTemplate("""
Summarize the following technical document in 3 bullet points:
{document}
""");
SystemMessage systemMsg = new SystemMessage(
systemTemplate.render(Map.of("language", language))
);
UserMessage userMsg = new UserMessage(
userTemplate.render(Map.of("document", document))
);
return chatClient.prompt(new Prompt(List.of(systemMsg, userMsg)))
.call()
.content();
}
}
Injecting Templates as Spring Beans
Define templates as beans in a configuration class to avoid creating new instances on every call:
@Configuration
public class PromptConfig {
@Bean
@Description("Template for generating Java code examples")
public PromptTemplate codeGeneratorTemplate() {
return new PromptTemplate("""
Generate a complete, working Java {version} example for: {requirement}
Requirements:
- Use Spring Boot 3.x
- Include all necessary imports
- Add brief comments explaining key lines
- Show expected output in a comment at the bottom
""");
}
}
@Service
public class CodeGenService {
private final ChatClient chatClient;
private final PromptTemplate codeGeneratorTemplate;
public CodeGenService(ChatClient.Builder builder, PromptTemplate codeGeneratorTemplate) {
this.chatClient = builder.build();
this.codeGeneratorTemplate = codeGeneratorTemplate;
}
public String generate(String requirement) {
Prompt prompt = codeGeneratorTemplate.create(Map.of(
"version", "17",
"requirement", requirement
));
return chatClient.prompt(prompt).call().content();
}
}
Template with Default Values
public String generateWithDefaults(String topic) {
// Use ternary in caller or provide defaults through Map
Map<String, Object> vars = new HashMap<>();
vars.put("topic", topic);
vars.put("length", "500 words");
vars.put("audience", "intermediate Java developers");
PromptTemplate template = new PromptTemplate(
"Write a {length} article about {topic} for {audience}."
);
return chatClient.prompt(template.create(vars)).call().content();
}
Output Example
// Input: generateTutorial("Spring AI RAG", "beginner")
// Rendered prompt sent to LLM:
// "Write a beginner tutorial about Spring AI RAG for Java developers.
// Include a working code example and explain the output."
// AI Response:
// Spring AI RAG Tutorial for Beginners
// =====================================
// RAG stands for Retrieval Augmented Generation...
// [complete tutorial content]
Key Points
PromptTemplateuses{placeholder}syntax — curly braces around variable namestemplate.create(Map.of(...))returns aPromptready to pass tochatClient.prompt()template.render(Map.of(...))returns the filled-inString— useful for buildingMessageobjects manually- Store large prompt templates in
src/main/resources/prompts/*.stfiles and load withClassPathResource - Define reusable templates as Spring beans to avoid repeated object creation
Comments