Table of Contents

Prompt Templates in Spring AI: Using .st Files with Parameters

  • Last Updated: September 27, 2026
  • By: javahandson
  • Series
img

Prompt Templates in Spring AI: Using .st Files with Parameters

Learn prompt templates in Spring AI 2.0.1 with .st files: load templates as a Resource, pass parameters, use system templates and fix JSON delimiters.

VERSION NOTE

Tested with Spring AI 2.0.1 on Spring Boot 4.1.1 and Java 21. Most older tutorials online still show Spring AI 1.x code, so check the version before you copy anything.

1. Introduction

Prompt templates in Spring AI help you stop writing long prompts inside Java strings. Instead, you keep the prompt in a separate .st file and fill in the changing values at runtime. In this guide, you will learn how templates work and how to load .st files. You will also learn how to pass parameters to them.

In my previous articles, we built a simple chat app with ChatClient. We also connected OpenAI and Ollama in the same project. However, all our prompts were plain strings typed right inside the controller. That works for a demo. But in a real project, it quickly becomes messy.

Here is what we will cover in this article:

  • What a prompt template is and why you need one
  • How Spring AI renders templates behind the scenes
  • Creating .st files and passing parameters to them
  • Using templates for both user and system messages
  • Working with the PromptTemplate class directly
  • Handling JSON inside templates with custom delimiters
  • What happens when you forget a parameter

We will build one small example throughout the article. It is a Java interview question generator. The user picks a topic, a level and a count. After that, our app sends a clean, templated prompt to the LLM.

2. What Is a Prompt Template in Spring AI?

A prompt template is a piece of text with placeholders inside it. Each placeholder is a name wrapped in curly braces, like {topic}. At runtime, Spring AI replaces every placeholder with a real value and then sends the final text to the model.

Think of it like a form letter. The letter body stays the same for everyone. Only the name, date and amount change for each person.

2.1 The Problem with Hardcoded Prompts

Let us first look at what most of us write on day one.

String prompt = "Generate " + count + " " + level
        + " level Java interview questions on " + topic
        + ". Give a short answer for each question.";

return chatClient.prompt(prompt).call().content();

This code works fine. Still, it has a few real problems:

  • The prompt text is mixed with Java logic, so it is hard to read.
  • A small wording change needs a code change, a build and a deployment.
  • Non-developers cannot review or improve the prompt easily.
  • Long prompts with many lines turn into a mess of + signs.
  • You cannot reuse the same prompt in another class without copying it.

2.2 How a Template Fixes It

Now look at the same prompt as a template.

Generate {count} {level} level Java interview questions on {topic}.
Give a short answer for each question.

The text is clean and readable. The placeholders clearly show which parts change. Moreover, you can move this text into its own file and never touch Java code to change the wording.

3. Why Use Prompt Templates in Spring AI?

The Spring AI course material I followed lists four simple reasons. I agree with all of them after using templates in my own projects.

  • Simplifies prompt construction: You write the prompt once and just fill in values.
  • Makes prompts reusable and maintainable: One template can serve many endpoints.
  • Keeps logic and text cleanly separated: Java code handles flow, and the .st file holds the words.
  • Supports parameterized placeholders: Values like {customerName} get filled at runtime.

On top of that, I would add two practical benefits from real work:

  • Easy prompt reviews: Your team can review prompt changes in a pull request, just like any other file.
  • Safer experiments: You can try a new prompt version by swapping a file, without changing the controller.

In short, a prompt is also a kind of source code. So it deserves its own file, its own history and its own review.

4. How Prompt Templates Work Under the Hood

Before writing code, it helps to know which classes do the actual work. Spring AI keeps this part quite simple.

How prompt templates in Spring AI work: an .st file is loaded as a Resource, StTemplateRenderer fills the parameters, and ChatClient sends the final message to the LLM

4.1 The PromptTemplate Class

PromptTemplate is the main class for templating in Spring AI. It holds your template text. It also knows how to turn that text into a final string, a Message or a full Prompt.

Even when you use the ChatClient fluent API, Spring AI uses PromptTemplate internally. So you use it every time, whether you see it or not.

4.2 TemplateRenderer and StTemplateRenderer

PromptTemplate does not replace the placeholders by itself. Instead, it hands that job to a TemplateRenderer.

public interface TemplateRenderer extends BiFunction<String, Map<String, Object>, String> {

    @Override
    String apply(String template, Map<String, Object> variables);
}

The default implementation is StTemplateRenderer. It is based on StringTemplate, an open-source template engine created by Terence Parr. That is also where the .st file extension comes from. It simply stands for StringTemplate.

By default, StTemplateRenderer uses these settings:

  • Start delimiter: {
  • End delimiter: }
  • Validation mode: THROW (we will see this in section 11)

4.3 NoOpTemplateRenderer

Sometimes your text has no placeholders at all. In that case, Spring AI gives you NoOpTemplateRenderer. It returns the text as it is, without any processing. This is handy when your prompt already contains many curly braces and you do not need any variables.

5. What Is an .st File?

An .st file is a plain text file that holds your prompt template. Nothing special is inside it. You write normal sentences and add placeholders wherever a value should change.

5.1 Where to Keep .st Files

The usual place is inside src/main/resources. This way, the file ends up on the classpath and Spring can load it easily. The course material I followed uses this path:

src/main/resources/promptTemplates/userPromptTemplate.st

For our example, we will use a similar structure with clear names:

src/main/resources/
└── promptTemplates/
    ├── interview-user.st
    ├── interview-system.st
    ├── review-system.st
    └── reviewer-system.st

5.2 Naming Tips

A few small habits make templates easier to manage later:

  • Put all templates in one folder, like promptTemplates or prompts.
  • Add the role to the file name, such as -system.st or -user.st.
  • Use lowercase names with hyphens so they sort nicely.
  • Keep one purpose per file. Do not mix two prompts in one template.

Spring AI does not force the .st extension. You can load a .txt file too, because Spring only sees it as a Resource. Even so, .st is the common convention, and your teammates will recognise it quickly.

5.3 What Makes a Good Template

A template is only as good as the words inside it. So, before we write code, here are a few habits I follow for every template:

  • Start with the role or task: The first line should tell the model what it is doing.
  • Give clear rules as a list: Models follow short bullet rules better than one long paragraph.
  • Describe the output format: Say exactly how the reply should look, such as numbered points or JSON.
  • Keep placeholders meaningful: A reader should guess the value just from the name.
  • Avoid hidden logic: Keep if and else decisions in Java, not in the template.

StringTemplate does support conditions and built-in functions. However, I keep templates plain on purpose. Plain text is easier to review, and anyone on the team can edit it without learning a new syntax.

6. Project Setup for Spring AI 2.0.1

Let us set up a small Spring Boot project. If you followed my first Spring AI article, you already have most of this.

6.1 Maven Dependencies

Spring AI 2.0.x supports Spring Boot 4.0.x and 4.1.x. We will import the Spring AI BOM and add the OpenAI starter.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.1</version>
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>2.0.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
</dependencies>

Notice the spring-boot-starter-webmvc artifact. In Spring Boot 4, this is the name of the web starter for Spring MVC apps. You do not need any extra dependency for templates. The StringTemplate support comes along with the Spring AI starter.

6.2 application.properties

Next, add your OpenAI key. Always read it from an environment variable and never commit it to Git.

spring.application.name=prompt-templates-demo
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.model=gpt-5-mini

Any chat model works here. For example, you can switch to Ollama and run everything locally. I covered that setup in my article on multiple chat models.

7. Using an .st File with Parameters in the User Message

This is the main part of the article. We will create a user template, load it as a resource, and fill its parameters at runtime.

7.1 Step 1: Create the User Template

Create the file src/main/resources/promptTemplates/interview-user.st with this content:

Generate {count} {level} level Java interview questions on the topic "{topic}".

For each question:
- Write the question in one line.
- Give a short answer in 3 to 4 simple sentences.
- Add one common mistake candidates make.

Number the questions and do not add any extra introduction.

There are three placeholders here: {count}, {level} and {topic}. Everything else is fixed text.

7.2 Step 2: Load the Template as a Resource

Spring can inject any classpath file as a Resource. So we use the @Value annotation with the classpath: prefix.

@Value("classpath:/promptTemplates/interview-user.st")
private Resource interviewUserTemplate;

At this point, Spring has not read the file text yet. It only holds a handle to the file. The actual reading happens when Spring AI builds the prompt.

7.3 Step 3: Pass Parameters with the User Spec

Now we pass the resource to .user() and add the values with .param(). Here is the full controller:

package com.javahandson.prompttemplates;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class InterviewController {

    private final ChatClient chatClient;

    @Value("classpath:/promptTemplates/interview-user.st")
    private Resource interviewUserTemplate;

    public InterviewController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/interview-questions")
    public String interviewQuestions(
            @RequestParam String topic,
            @RequestParam(defaultValue = "beginner") String level,
            @RequestParam(defaultValue = "3") int count) {

        return chatClient.prompt()
                .user(userSpec -> userSpec
                        .text(interviewUserTemplate)
                        .param("topic", topic)
                        .param("level", level)
                        .param("count", count))
                .call()
                .content();
    }
}

Let us understand the key lines:

  • The .user(userSpec -> ...) call gives you a PromptUserSpec to build the user message.
  • Inside it, .text(interviewUserTemplate) sets the template body from the .st file.
  • Each .param("topic", topic) call fills one placeholder, here {topic}.
  • Finally, .call().content() sends the request and returns the reply as a String.

Also notice that count is an int. The param() method accepts any Object, so you do not need to convert numbers to strings first.

If you have many values, you can pass them all at once with params():

.user(userSpec -> userSpec
        .text(interviewUserTemplate)
        .params(Map.of(
                "topic", topic,
                "level", level,
                "count", count)))

7.4 Step 4: Test the Endpoint

Start the app and call the endpoint with curl:

curl "http://localhost:8080/api/interview-questions?topic=Java%20Streams&level=intermediate&count=2"

Spring AI first renders the template. So the model receives this final text:

Generate 2 intermediate level Java interview questions on the topic "Java Streams".

For each question:
- Write the question in one line.
- Give a short answer in 3 to 4 simple sentences.
- Add one common mistake candidates make.

Number the questions and do not add any extra introduction.

The reply will look something like this. Your exact output will differ, because LLM answers change from run to run.

1. What is the difference between map() and flatMap() in Java Streams?
   map() transforms each element into exactly one new element...
   Common mistake: Using map() when each element returns a list...

2. Why are Java Streams called lazy?
   Intermediate operations do not run until a terminal operation...
   Common mistake: Expecting peek() to print without a terminal operation...

Tip: If your template has non-English text, pass the charset too, like .text(resource, StandardCharsets.UTF_8). This avoids strange characters on servers with a different default encoding.

8. Using an .st File for the System Message

User messages carry the question. System messages, on the other hand, set the behaviour and tone of the assistant. Templates work for both roles in exactly the same way.

8.1 Create the System Template

Create src/main/resources/promptTemplates/interview-system.st:

You are a senior Java interviewer at a {companyType} company.
You ask practical questions that test real project experience.
Keep your language simple and friendly.
If the topic is not related to Java or its ecosystem, politely say
that you only handle Java interview topics.

8.2 Pass Parameters to the System Spec

The system spec works just like the user spec. You call .text() with the resource and add values with .param().

@Value("classpath:/promptTemplates/interview-system.st")
private Resource interviewSystemTemplate;

@GetMapping("/interview-questions/v2")
public String interviewQuestionsV2(
        @RequestParam String topic,
        @RequestParam(defaultValue = "beginner") String level,
        @RequestParam(defaultValue = "3") int count,
        @RequestParam(defaultValue = "product") String companyType) {

    return chatClient.prompt()
            .system(systemSpec -> systemSpec
                    .text(interviewSystemTemplate)
                    .param("companyType", companyType))
            .user(userSpec -> userSpec
                    .text(interviewUserTemplate)
                    .param("topic", topic)
                    .param("level", level)
                    .param("count", count))
            .call()
            .content();
}

Now one request has two templates. The system template decides the persona, and the user template carries the actual task. Each template gets its own parameters, so there is no clash between them.

8.3 Set a Default System Template Once

Most apps use the same system prompt for every request. In that case, you can set it once on the builder with defaultSystem(Resource). Then, at request time, you only pass the parameter.

@Configuration
public class ChatClientConfig {

    @Value("classpath:/promptTemplates/interview-system.st")
    private Resource interviewSystemTemplate;

    @Bean
    public ChatClient interviewChatClient(ChatClient.Builder builder) {
        return builder
                .defaultSystem(interviewSystemTemplate)
                .build();
    }
}

And in the controller, you just fill in the system parameter:

return interviewChatClient.prompt()
        .system(systemSpec -> systemSpec.param("companyType", companyType))
        .user(userSpec -> userSpec
                .text(interviewUserTemplate)
                .param("topic", topic)
                .param("level", level)
                .param("count", count))
        .call()
        .content();

Spring AI also offers defaultUser(Resource) if you need a default user template. Any value you pass at request time overrides the default, so you stay in control.

9. Using the PromptTemplate Class Directly

The fluent API covers most needs. However, sometimes you want the rendered text on its own. For example, you may want to log it, test it, or send it through ChatModel directly. That is where the PromptTemplate class helps.

9.1 Create a Prompt from a Template

Here is the simplest way to create a Prompt object from a template:

PromptTemplate promptTemplate = new PromptTemplate(
        "Explain {concept} to a {level} Java developer in 5 lines.");

Prompt prompt = promptTemplate.create(Map.of(
        "concept", "virtual threads",
        "level", "beginner"));

String answer = chatModel.call(prompt)
        .getResult()
        .getOutput()
        .getText();

Here, create(Map) returns a full Prompt that you can pass to ChatModel. It also works with ChatClient, because chatClient.prompt(prompt) accepts a Prompt too.

9.2 Use the Builder with a Resource

For file-based templates, the builder is the cleanest option. It accepts a Resource, a map of variables and an optional renderer.

PromptTemplate promptTemplate = PromptTemplate.builder()
        .resource(interviewUserTemplate)
        .variables(Map.of(
                "topic", "Spring Boot Actuator",
                "level", "advanced",
                "count", 3))
        .build();

String finalText = promptTemplate.render();

The render() method returns the final text as a String. I use this a lot while debugging. You can log the exact prompt before it goes to the model and quickly spot mistakes.

9.3 The Three Ways to Get Output

PromptTemplate gives you three kinds of output, depending on what you need next.

MethodReturnsWhen to use it
render() / render(Map)StringLogging, testing or sending plain text
createMessage(Map)MessageBuilding a list of messages yourself
create(Map) / create(Map, ChatOptions)PromptCalling ChatModel directly with optional options

9.4 SystemPromptTemplate

For system messages, Spring AI has a small subclass called SystemPromptTemplate. Its createMessage() method returns a SystemMessage instead of a user message.

SystemPromptTemplate systemTemplate = new SystemPromptTemplate(interviewSystemTemplate);
Message systemMessage = systemTemplate.createMessage(Map.of("companyType", "fintech"));

Message userMessage = new UserMessage("Ask me one question on Java records.");

Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
String reply = chatClient.prompt(prompt).call().content();

9.5 Fluent API or PromptTemplate Class?

Both approaches use the same engine, so the choice is mostly about style.

SituationBetter choice
Normal REST endpoint with ChatClientFluent .user() / .system() with .param()
You need the rendered text for logs or testsPromptTemplate.render()
You call ChatModel directlyPromptTemplate.create()
You build a custom message listcreateMessage() or SystemPromptTemplate

My simple rule is this. Start with the fluent API. Move to the PromptTemplate class only when you need the rendered text or the Prompt object in your own hands.

10. Handling JSON in Templates with Custom Delimiters

Here is a problem that surprises almost everyone once. You want the model to reply in JSON, so you add a sample JSON inside the template. Suddenly, rendering fails.

10.1 Why Curly Braces Cause Trouble

By default, { and } mark placeholders. So the renderer treats every curly brace in your JSON as the start of a variable. As a result, it either throws an error or produces broken text.

Return the answer in this format:
{ "question": "...", "answer": "..." }

The renderer cannot tell which braces are JSON and which are placeholders.

10.2 Switch to Angle Brackets

The fix is to change the delimiters. StTemplateRenderer has a builder where you can set your own start and end tokens.

Create a new template, review-system.st, which uses angle brackets for placeholders:

You review Java code for a <teamName> team.
Follow these team rules:
<guidelines>

Reply only in this JSON format:
{ "verdict": "APPROVE or CHANGES_NEEDED", "comments": ["..."] }

Now tell ChatClient to use angle-bracket delimiters for this call:

// reviewSystemTemplate is loaded with @Value, just like before
String review = chatClient.prompt()
        .system(systemSpec -> systemSpec
                .text(reviewSystemTemplate)
                .param("teamName", "payments")
                .param("guidelines", guidelines))
        .user(code)
        .templateRenderer(StTemplateRenderer.builder()
                .startDelimiterToken('<')
                .endDelimiterToken('>')
                .build())
        .call()
        .content();

Now <teamName> and <guidelines> are placeholders, and the JSON braces stay as normal text.

10.3 Things to Remember About Delimiters

  • The delimiter tokens are single characters. So {{ and }} are not supported, because the builder methods take a char.
  • You can set a renderer for all calls with defaultTemplateRenderer(...) on ChatClient.Builder.
  • The same builder works with PromptTemplate.builder().renderer(...) too.
  • The renderer set on ChatClient only affects text you pass through .user() and .system(). Advisors like QuestionAnswerAdvisor have their own template settings.

11. What Happens When a Parameter Is Missing?

Sooner or later, someone adds a new placeholder to a template but forgets to pass the value. So how does Spring AI react?

11.1 Default Mode: THROW

By default, the renderer validates the template before rendering. If any placeholder has no value, it throws an IllegalStateException. The message looks like this:

Not all variables were replaced in the template. Missing variable names are: [level].

Honestly, this is a good default. A missing value usually means a bug. Failing fast is better than sending a half-filled prompt to a paid model.

11.2 WARN and NONE Modes

You can change this behaviour with the ValidationMode enum. It has three values:

ModeWhat happens on a missing variable
THROWThrows an exception. This is the default.
WARNLogs a warning and renders the template with the gap. Not recommended for production.
NONESkips validation completely.

Here is how to set it:

ChatClient chatClient = builder
        .defaultTemplateRenderer(StTemplateRenderer.builder()
                .validationMode(ValidationMode.WARN)
                .build())
        .build();

I suggest you keep THROW in production. Use WARN only while you are shaping a new template locally.

11.3 A Gotcha: No Params Means No Rendering

This one is easy to miss. ChatClient renders the user or system text only when you pass at least one parameter for it. I confirmed this in the Spring AI 2.0.1 source code of DefaultChatClientUtils.

So what happens if you call .user(u -> u.text(template)) and forget every .param() call? Spring AI skips rendering completely. No exception is thrown. Instead, the model receives the raw text with {topic} and {level} still inside it.

The same thing happens with a default system template. If a request never calls .system(sp -> sp.param(...)), the placeholders go to the model as they are. Therefore, always pass the parameters, and add a render test like the one in section 13.

12. Prompt Templates and Prompt Stuffing

Templates are also the easiest way to do prompt stuffing. The course material describes prompt stuffing as giving the LLM an open book before it answers. You add reference text to the prompt, and the model uses it to answer. This works even for things the model never saw during training.

For example, say your company has its own Java coding rules. The model does not know them. So you stuff those rules into the system template through a placeholder.

You are a code reviewer for our team.
Use only the rules given below to review the code.
If a rule is not listed, do not invent one.

Team rules:
{guidelines}
@Value("classpath:/promptTemplates/reviewer-system.st")
private Resource reviewerTemplate;

@Value("classpath:/docs/java-coding-rules.txt")
private Resource codingRules;

public String review(String code) throws IOException {
    String guidelines = codingRules.getContentAsString(StandardCharsets.UTF_8);

    return chatClient.prompt()
            .system(systemSpec -> systemSpec
                    .text(reviewerTemplate)
                    .param("guidelines", guidelines))
            .user(code)
            .call()
            .content();
}

This works well for small documents. However, stuffing a very large document into every request wastes tokens and can go past the model’s context window. For large data, the better approach is RAG, where only the most relevant chunks are added to the prompt. We will cover RAG in a later article in this series.

13. Common Mistakes and Best Practices for Prompt Templates in Spring AI

After building a few Spring AI apps, I have made most of these mistakes myself. Here is what to watch out for.

13.1 Common Mistakes

  • Wrong placeholder name: The template says {topic} but the code passes "topics". The default THROW mode will catch this, so read the error message carefully.
  • Wrong classpath path: A typo in classpath:/promptTemplates/... gives a missing-file error only at request time. So test every endpoint once after adding a template.
  • JSON braces with default delimiters: Switch to < and > as shown in section 10.
  • Mixing user input into the template text: Say you glue raw user input into the template and then pass params. Now any braces typed by the user get parsed as placeholders. Instead, pass user input as a parameter value or as a plain .user(String) call.
  • Forgetting all params: With zero .param() calls, the template is not rendered at all, as we saw in section 11.3.
  • Mixing two templates in one file: Keep system and user templates separate.

13.2 Best Practices

  • Keep all templates under one folder in src/main/resources.
  • Name placeholders clearly, like {customerName} instead of {n}.
  • Log the rendered prompt with PromptTemplate.render() while you develop.
  • Review prompt changes in pull requests, just like code.
  • Keep ValidationMode.THROW in production.
  • Write a small unit test that renders each template with sample values.

A render test is quick to write, and it needs no API key:

@Test
void interviewTemplateRendersAllPlaceholders() {
    Resource resource =
            new ClassPathResource("promptTemplates/interview-user.st");

    String text = PromptTemplate.builder()
            .resource(resource)
            .variables(Map.of(
                    "topic", "Java Records",
                    "level", "beginner",
                    "count", 2))
            .build()
            .render();

    assertThat(text).contains("Generate 2 beginner level");
    assertThat(text).doesNotContain("{");
}

This test catches broken placeholders before your users do. It also runs in milliseconds, because no model is called.

14. FAQ’s on prompt templates in Spring AI

Q: What is a prompt template in Spring AI?

A: A prompt template is text with named placeholders like {topic}. At runtime, Spring AI replaces each placeholder with a real value using PromptTemplate and the default StTemplateRenderer, then sends the final text to the model.

Q: What is an .st file in Spring AI?

A: An .st file is a plain text StringTemplate file that holds a prompt. You keep it in src/main/resources, load it with @Value(“classpath:…”) as a Resource, and fill its placeholders with .param() in ChatClient.

Q: How do I pass parameters to a prompt template in Spring AI?

A: Use chatClient.prompt().user(u -> u.text(resource).param(“topic”, topic)). The same pattern works for system messages with .system(s -> s.text(resource).param(…)). You can also pass a Map with .params().

Q: How do I use JSON inside a Spring AI prompt template?

A: The default delimiters are { and }, so JSON braces break rendering. Configure StTemplateRenderer.builder().startDelimiterToken(‘<‘).endDelimiterToken(‘>’).build() and pass it with .templateRenderer() or defaultTemplateRenderer().

Q: What happens if I forget a template parameter in Spring AI?

A: By default, ValidationMode.THROW raises an IllegalStateException that lists the missing variables. However, if you pass no parameters at all, ChatClient skips rendering and the raw placeholders reach the model.

Q: What is the difference between PromptTemplate and SystemPromptTemplate?

A: Both render templates the same way. PromptTemplate.createMessage() returns a user message, while SystemPromptTemplate.createMessage() returns a SystemMessage for setting the assistant’s behaviour.

15. Conclusion

Prompt templates in Spring AI turn messy string building into clean, reusable files. You keep the prompt in an .st file, load it as a Resource, and fill its placeholders with .param(). The same idea works for user messages, system messages and default prompts on the builder.

Let us quickly recap the key points:

  • PromptTemplate handles templating, and StTemplateRenderer does the actual rendering.
  • .st files live in src/main/resources and load through @Value("classpath:...").
  • Use .user(u -> u.text(resource).param(...)) and .system(s -> s.text(resource).param(...)) in ChatClient.
  • Use the PromptTemplate class when you need the rendered text or a Prompt object.
  • Switch to single-character delimiters like < and > when your template contains JSON.
  • Missing parameters throw an IllegalStateException by default, and that is a good thing.

Once you start using .st files, you will rarely go back to string concatenation. Your prompts become easier to read, easier to test and easier to improve with your team.

16. Further Reading

Leave a Comment