Artificial Intelligence 18 min read

Mastering Prompt Engineering with Spring AI: Patterns and Practical Java Examples

An in‑depth guide shows how to configure Spring AI for various LLM providers, tune model parameters such as temperature and max tokens, and apply a range of prompt‑engineering patterns—including zero‑shot, few‑shot, chain‑of‑thought, self‑consistency, role‑based and automatic prompting—using concise Java code examples.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Mastering Prompt Engineering with Spring AI: Patterns and Practical Java Examples

Translated from the official Spring blog, this article demonstrates how Spring AI simplifies prompt engineering for Java developers using the DeepSeek Chat model.

Preface

With the rapid development of large language models (LLMs), designing effective prompts has become a crucial skill. Spring AI offers a clean, powerful API that makes implementing prompt‑engineering techniques straightforward and efficient.

1. Configuration and Initialization

Select LLM Provider

Spring AI supports multiple providers (OpenAI, Anthropic, Google Vertex AI, AWS Bedrock, Ollama, etc.) without changing application code. Add the appropriate starter dependency:

<code>spring-ai-starter-model-<provider></code>

Example for OpenAI:

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
  &lt;artifactId&gt;spring-ai-starter-model-openai&lt;/artifactId&gt;
&lt;/dependency&gt;</code>

Configure connection properties:

<code>spring.ai.openai.api-key=sk-xxxxxx
spring.ai.openai.chat.options.model=deepseek-chat
spring.ai.openai.base-url=https://api.deepseek.com/v1</code>

You can also set model version directly:

<code>.options(ChatOptions.builder()
        .model("deepseek-chat")
        .build())</code>

LLM Output Configuration

Spring AI provides a

ChatOptions

builder to control various aspects of model output.

Temperature

Temperature controls the randomness or "creativity" of the model’s responses:

Low (0.0‑0.3): deterministic, suitable for factual or classification tasks.

Medium (0.4‑0.7): balanced between determinism and creativity for general use.

High (0.8‑1.0): more creative and diverse, ideal for creative writing or brainstorming.

<code>.options(ChatOptions.builder()
        .temperature(0.1) // very deterministic output
        .build())</code>

MaxTokens (Output Length)

The

maxTokens

parameter limits the number of tokens the model can generate:

Low (5‑25): single words or short labels.

Medium (50‑500): paragraphs or brief explanations.

High (1000+): long content, stories, or complex explanations.

<code>.options(ChatOptions.builder()
        .maxTokens(250) // medium‑length answer
        .build())</code>

Top‑K and Top‑P (Sampling Control)

These parameters provide fine‑grained control over token selection during generation:

Top‑K : limit selection to the K most probable next tokens.

Top‑P (nucleus sampling): consider the smallest set of tokens whose cumulative probability exceeds P.

<code>.options(ChatOptions.builder()
        .topK(40)   // consider only top 40 tokens
        .topP(0.8)  // sample from tokens covering 80% probability mass
        .build())</code>

Structured Response Format

Beyond plain‑text

.content()

, Spring AI can map LLM responses directly to Java objects:

<code>enum Sentiment { POSITIVE, NEUTRAL, NEGATIVE }

Sentiment result = chatClient.prompt("...")
        .call()
        .entity(Sentiment.class);
</code>

Model‑Specific Options

While

ChatOptions

offers a unified interface, provider‑specific option classes expose additional features. Example with OpenAI:

<code>OpenAiChatOptions openAiOptions = OpenAiChatOptions.builder()
        .model("gpt-4o")
        .temperature(0.2)
        .responseFormat(new ResponseFormat("json_object"))
        .build();
</code>
1744730525
1744730525

2. Prompt Engineering Techniques

2.1 Zero‑Shot Prompting

Zero‑shot prompting asks the model to perform a task without providing examples, testing its ability to follow instructions from scratch.

<code>public void pt_zero_shot_prompting(ChatClient chatClient) {
    String translation = chatClient.prompt("将以下英文文本翻译成中文:'Spring AI makes prompt engineering easy'")
            .call()
            .content();

    String classification = chatClient.prompt("判断以下评论的情感是正面、中性还是负面:'这家餐厅的服务太差了,但是食物还不错 '")
            .options(ChatOptions.builder().temperature(0.1).build())
            .call()
            .content();
}
</code>

2.2 Few‑Shot Prompting

Few‑shot prompting provides a few examples to guide the model, especially useful for tasks requiring specific formats.

<code>public void pt_few_shot_prompting(ChatClient chatClient) {
    String fewShotClassification = chatClient.prompt("""
        将以下产品评论分类为正面或负面:
        评论:"这款手机太棒了,拍照效果非常好!"
        分类:正面

        评论:"送货慢,包装破损,很失望。"
        分类:负面

        评论:"使用一周后电池开始出现问题,不推荐购买。"
        分类:
        """)
            .call()
            .content();
}
</code>

2.3 Chain‑of‑Thought Prompting

Chain‑of‑thought prompting encourages the model to reason step‑by‑step, improving accuracy on complex problems.

<code>public void pt_chain_of_thought_prompting(ChatClient chatClient) {
    String solution = chatClient.prompt("""
        小明有 5 个苹果,小红给了他 2 个苹果,然后他吃掉了 3 个,又送给了小华 1 个。
        小明现在有多少个苹果?请一步一步思考。
        """)
            .call()
            .content();
}
</code>

2.4 Self‑Consistency Prompting

Self‑consistency generates multiple independent solutions and selects the most common answer, boosting reliability for critical decisions.

<code>public void pt_self_consistency_prompting(ChatClient chatClient) {
    List<String> solutions = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        String solution = chatClient.prompt("""
            一个商店正在进行 "买 2 送 1" 的促销活动。如果每件商品原价 15 元,买 7 件需要多少钱?请逐步思考。
            """)
                .options(ChatOptions.builder().temperature(0.7).build())
                .call()
                .content();
        solutions.add(solution);
    }
    String finalSolution = chatClient.prompt()
        .user(u -> u.text("""
            分析以下解决方案,选择最准确的一个:
            {solutions}
            """).param("solutions", String.join("\n---\n", solutions)))
        .options(ChatOptions.builder().temperature(0.1).build())
        .call()
        .content();
}
</code>

2.5 Tree‑of‑Thoughts Prompting

Tree‑of‑thoughts extends chain‑of‑thought by exploring multiple reasoning branches, useful for forward‑looking problems.

<code>public void pt_tree_of_thoughts_prompting(ChatClient chatClient) {
    String solution = chatClient.prompt("""
        你正在玩数独游戏,需要填写 3x3 网格的最后一行。
        已知数据:
        第一行:1 5 9
        第二行:6 7 3
        第三行:? ? ?
        请探索不同可能性,考虑每种选择带来的约束,找出符合数独规则的第三行数字组合。
        """)
        .call()
        .content();
}
</code>

2.6 Abstractions in Prompting

Abstraction prompts ask the model to step back, understand the problem at a higher level, then provide a concrete solution.

<code>public void pt_abstractions_in_prompting(ChatClient chatClient) {
    String solution = chatClient.prompt("""
        解决这个编程问题:实现一个函数,找出数组中所有和为特定值的数字对。
        在回答之前,先思考这个问题的核心是什么,可以用什么算法解决,哪些边界情况需要考虑,然后再提供解决方案。
        """)
        .call()
        .content();
}
</code>

2.7 Role Prompting

Role prompting assigns a specific persona to the model, leveraging domain‑specific knowledge.

<code>public void pt_role_prompting(ChatClient chatClient) {
    // System‑message role
    String roleWithSystem = chatClient.prompt()
        .system("你是一位经验丰富的 Java 开发专家,尤其擅长 Spring 框架")
        .user("解释一下 Spring AOP 的原理和使用场景")
        .call()
        .content();

    // Role defined directly in user prompt
    String roleInPrompt = chatClient.prompt("""
        作为一名资深数据库架构师,请评估将传统关系型数据库迁移到 NoSQL 数据库的优缺点,特别是对于一个高流量的电子商务网站。考虑性能、可扩展性和一致性等方面。
        """)
        .call()
        .content();
}
</code>

2.8 Automatic Prompt Engineering

Automatic Prompt Engineering (APE) uses an LLM to generate and evaluate prompt variants, selecting the best one automatically.

<code>public void pt_automatic_prompt_engineering(ChatClient chatClient) {
    // Generate instruction variants
    String orderVariants = chatClient.prompt("""
        我们有一个乐队周边 T 恤网店,为了训练聊天机器人,需要多种下单方式的表述:"一件 S 码的 Metallica T 恤"。请生成 10 个语义相同但表达不同的变体。
        """)
        .options(ChatOptions.builder().temperature(1.0).build())
        .call()
        .content();

    // Evaluate and select the best variant
    String output = chatClient.prompt()
        .user(u -> u.text("""
            请对以下变体进行 BLEU 评估:
            ----
            {variants}
            ----
            选择评分最高的指令候选。
            """).param("variants", orderVariants))
        .call()
        .content();
}
</code>

2.9 Code Prompting

Code prompting targets programming tasks such as code generation, explanation, and translation.

<code>// Writing code
public void pt_code_prompting_writing_code(ChatClient chatClient) {
    String bashScript = chatClient.prompt("""
        用 Bash 编写一个脚本,询问用户输入文件夹名称,然后将该文件夹中的所有文件重命名,在文件名前加上 "draft_" 前缀。
        """)
        .options(ChatOptions.builder().temperature(0.1).build())
        .call()
        .content();
}

// Explaining code
public void pt_code_prompting_explaining_code(ChatClient chatClient) {
    String code = """
        #!/bin/bash
        echo \"输入文件夹名称:\"
        read folder_name
        if [ ! -d \"$folder_name\" ]; then
          echo \"文件夹不存在。\"
          exit 1
        fi
        files=( \"$folder_name\"/* )
        for file in "${files[@]}"; do
          new_file_name=\"draft_$(basename \"$file\")\"
          mv \"$file\" \"$new_file_name\"
        done
        echo \"文件重命名成功。\"
        """;
    String explanation = chatClient.prompt()
        .user(u -> u.text("""
            解释以下 Bash 代码的功能:
            ```
            {code}
            ```
            """).param("code", code))
        .call()
        .content();
}

// Translating code
public void pt_code_prompting_translating_code(ChatClient chatClient) {
    String bashCode = """
        #!/bin/bash
        echo \"输入文件夹名称:\"
        read folder_name
        if [ ! -d \"$folder_name\" ]; then
          echo \"文件夹不存在。\"
          exit 1
        fi
        files=( \"$folder_name\"/* )
        for file in "${files[@]}"; do
          new_file_name=\"draft_$(basename \"$file\")\"
          mv \"$file\" \"$new_file_name\"
        done
        echo \"文件重命名成功。\"
        """;
    String pythonCode = chatClient.prompt()
        .user(u -> u.text("""
            将以下 Bash 代码翻译成 Python:
            {code}
            """).param("code", bashCode))
        .call()
        .content();
}
</code>

Conclusion

Spring AI provides an elegant Java API for implementing a wide variety of prompt‑engineering techniques. Combining multiple patterns—such as system prompts with few‑shot examples or chain‑of‑thought with role prompting—yields more robust and higher‑quality AI applications.

Test different parameters (temperature, top‑k, top‑p) to observe their impact on prompt performance.

Consider self‑consistency for critical decision‑making tasks.

Leverage Spring AI’s entity mapping for type‑safe responses.

Use context prompts to inject application‑specific knowledge.

JavaLLMPrompt EngineeringSpring AIChatOptions
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.