One of the biggest challenges in working with large language models (LLMs) is ensuring that the model responds in the way your application needs.
Free-form text answers are fun to experiment with – but in real-world systems, you often need more structure and reusability.
That’s where two powerful Spring AI features come in:
- Prompt Templates – Reusable prompts with dynamic placeholders;
- Structured Outputs – Machine-readable JSON responses, directly mapped to Java objects.
1.Prompt Templates
A PromptTemplate is like a format string for prompts. Instead of hardcoding text, you define placeholders that get filled in at runtime.
This makes your prompts:
- Reusable across multiple use cases
- Maintainable if wording changes
- Safer since you avoid string concatenation
Example:
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.openai.OpenAiChatClient;
import java.util.Map;
public class PromptTemplateExample {
private final OpenAiChatClient chatClient;
public PromptTemplateExample(OpenAiChatClient chatClient) {
this.chatClient = chatClient;
}
public String translate(String text, String language) {
PromptTemplate template = new PromptTemplate("Translate the following sentence into {language}: {text}");
UserMessage message = template.createMessage(Map.of(
"language", language,
"text", text));
return chatClient.call(message);
}
public static void main(String[] args) {
OpenAiChatClient chatClient = OpenAiChatClient.builder().build();
PromptTemplateExample example = new PromptTemplateExample(chatClient);
String result = example.translate("Hello, how are you?", "French");
System.out.println(result);
}
}
/*
* Output:
* Bonjour, comment ça va?
*/
2.Structured Outputs
By default, LLMs return plain text.
But what if your app needs structured data – like a JSON object that maps directly to a DTO?
Spring AI’s BeanOutputParser helps you guide the model to respond in valid JSON, and then parse it straight into Java objects.
Example:
import java.util.Map;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.ai.parser.BeanOutputParser;
public class StructuredOutputExample {
private static class Person {
private String name;
private int age;
private String city;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setCity(String city) {
this.city = city;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getCity() {
return city;
}
}
public static void main(String[] args) {
BeanOutputParser<Person> parser = new BeanOutputParser<>(Person.class);
String formattedPrompt = parser.getFormat(); // ensures model outputs correct JSON format
PromptTemplate template = new PromptTemplate("Extract person info from the text: {input}");
Map<String, Object> params = Map.of(
"input", "My name is Alice, I am 30 years old, and I live in Paris.");
OpenAiChatClient chatClient = OpenAiChatClient.builder().build();
ChatResponse response = chatClient.call(template.createMessage(params, formattedPrompt));
Person person = parser.parse(response.getResult().getOutput().getContent());
System.out.println(person.getName());
System.out.println(person.getAge());
System.out.println(person.getCity());
}
}
/*
* Output:
* Alice
* 30
* Paris
*/
3.Real World Example – Customer Support Ticket Extraction
Imagine a support system where users submit free-form text describing their issue.
You want to automatically extract structured fields like category, priority, and summary.
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.parser.BeanOutputParser;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/ticket")
public class TicketController {
private final OpenAiChatClient chatClient;
public TicketController(OpenAiChatClient chatClient) {
this.chatClient = chatClient;
}
@PostMapping
public SupportTicket extractTicket(@RequestBody String issueDescription) {
PromptTemplate template = new PromptTemplate(
"Analyze the support request: {text}. " +
"Extract fields: category, priority (low, medium, high), summary.");
BeanOutputParser<SupportTicket> parser = new BeanOutputParser<>(SupportTicket.class);
String format = parser.getFormat();
ChatResponse response = chatClient.call(
template.createMessage(Map.of("text", issueDescription), format));
return parser.parse(response.getResult().getOutput().getContent());
}
private static class SupportTicket {
private String category;
private String priority;
private String summary;
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getPriority() {
return priority;
}
public void setPriority(String priority) {
this.priority = priority;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
}
}
Example Request:
curl -X POST http://localhost:8080/ticket \
-H "Content-Type: text/plain" \
-d "My internet is down since yesterday and I cannot work. Please fix this urgently."
Possible AI Response – Parsed into SupportTicket:
{
"category": "Internet",
"priority": "high",
"summary": "Internet outage preventing user from working"
}
Conclusion
- Prompt Templates keep prompts flexible and maintainable.
- Structured Outputs ensure AI responses can directly feed into your applications.
Together, they transform AI from a “chat toy” into a robust backend tool that integrates seamlessly with enterprise Java systems.