Java SpringAI

Spring AI Prompt Data Binding — Inject Java Objects into AI Prompts

Spring AI Prompt Data Binding — Inject Java Objects into AI Prompts

In real applications, AI prompts are rarely static. You need to inject Java objects — user profiles, product data, request parameters — into prompt templates to generate personalized and context-aware responses. Spring AI's PromptTemplate supports data binding from Maps and Java objects, making prompt construction as natural as working with any Spring template engine.

Simple Map Binding

@Service
public class PersonalizedResponseService {

    private final ChatClient chatClient;

    public PersonalizedResponseService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String generateWelcome(String name, String role, String company) {
        PromptTemplate template = new PromptTemplate("""
                Write a personalized onboarding message for:
                Name    : {name}
                Role    : {role}
                Company : {company}

                Keep it professional, encouraging, and under 100 words.
                """);

        Prompt prompt = template.create(Map.of(
                "name",    name,
                "role",    role,
                "company", company
        ));

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

Output

// generateWelcome("Ravi Kumar", "Senior Java Developer", "TechCorp")

Welcome, Ravi! We're thrilled to have you join TechCorp as a Senior Java Developer.
Your expertise will be invaluable to our engineering team. We look forward to
seeing the impact you'll bring to our projects. Feel free to reach out to your
team lead as you get started — we're all here to support your success. Welcome aboard!

Binding from Java Records and POJOs

// Domain objects
public record Product(
        String name,
        String category,
        double price,
        int    stockQuantity,
        String description
) {}

public record Customer(
        String name,
        String email,
        String preferredLanguage,
        List<String> purchaseHistory
) {}
@Service
public class ProductRecommendationService {

    private final ChatClient chatClient;

    public ProductRecommendationService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String recommend(Customer customer, List<Product> availableProducts) {
        // Build product list string for the template
        String productList = availableProducts.stream()
                .map(p -> "- %s (%s) $%.2f — %s"
                        .formatted(p.name(), p.category(), p.price(), p.description()))
                .collect(Collectors.joining("\n"));

        String purchaseHistory = String.join(", ", customer.purchaseHistory());

        PromptTemplate template = new PromptTemplate("""
                Recommend the top 3 products for customer {customerName}.

                Customer profile:
                - Name             : {customerName}
                - Language         : {language}
                - Purchase history : {history}

                Available products:
                {products}

                Explain WHY each product suits this customer in one sentence.
                Respond in {language}.
                """);

        Prompt prompt = template.create(Map.of(
                "customerName", customer.name(),
                "language",     customer.preferredLanguage(),
                "history",      purchaseHistory,
                "products",     productList
        ));

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

Dynamic Code Review with Object Binding

public record CodeReviewRequest(
        String  language,
        String  code,
        String  reviewType,    // "security" | "performance" | "style"
        boolean includeRefactored
) {}

@Service
public class CodeReviewService {

    private final ChatClient chatClient;

    public CodeReviewService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String review(CodeReviewRequest request) {
        PromptTemplate template = new PromptTemplate("""
                Perform a {reviewType} review of this {language} code.

                {refactoredSection}

                Code:
                ```{language}
                {code}
                ```

                Focus only on {reviewType} issues. Be specific — reference line numbers.
                """);

        String refactoredSection = request.includeRefactored()
                ? "After your review, provide the refactored version of the code."
                : "Provide findings only, no refactored code needed.";

        Prompt prompt = template.create(Map.of(
                "reviewType",       request.reviewType(),
                "language",         request.language(),
                "code",             request.code(),
                "refactoredSection", refactoredSection
        ));

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

Binding Lists and Collections

public String generateTestCases(String methodSignature, List<String> scenarios) {
    String scenarioList = IntStream.range(0, scenarios.size())
            .mapToObj(i -> (i + 1) + ". " + scenarios.get(i))
            .collect(Collectors.joining("\n"));

    PromptTemplate template = new PromptTemplate("""
            Generate JUnit 5 test cases for this Java method:

            Method: {method}

            Test these specific scenarios:
            {scenarios}

            Use AssertJ assertions. Include arrange/act/assert comments.
            """);

    Prompt prompt = template.create(Map.of(
            "method",    methodSignature,
            "scenarios", scenarioList
    ));

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

Template from External File with Object Data

File: src/main/resources/prompts/incident-report.st

You are a DevOps incident commander. Analyze this production incident:

Incident ID  : {incidentId}
Severity     : {severity}
Service      : {serviceName}
Start Time   : {startTime}
Error Rate   : {errorRate}%
Error Message: {errorMessage}

Provide:
1. Root cause hypothesis (2-3 sentences)
2. Immediate mitigation steps (numbered list)
3. Long-term fix recommendation
4. Post-mortem action items
public record IncidentReport(
        String incidentId, String severity, String serviceName,
        String startTime, double errorRate, String errorMessage
) {}

public String analyzeIncident(IncidentReport incident) {
    PromptTemplate template = new PromptTemplate(
            new ClassPathResource("prompts/incident-report.st"));

    Prompt prompt = template.create(Map.of(
            "incidentId",   incident.incidentId(),
            "severity",     incident.severity(),
            "serviceName",  incident.serviceName(),
            "startTime",    incident.startTime(),
            "errorRate",    incident.errorRate(),
            "errorMessage", incident.errorMessage()
    ));

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

Controller Example

@RestController
@RequestMapping("/ai")
public class DataBindingController {

    private final ProductRecommendationService recommendationService;
    private final CodeReviewService            codeReviewService;

    @PostMapping("/recommend")
    public String recommend(@RequestBody RecommendRequest req) {
        return recommendationService.recommend(req.customer(), req.products());
    }

    @PostMapping("/review")
    public String review(@RequestBody CodeReviewRequest req) {
        return codeReviewService.review(req);
    }
}

Key Points

  • Always escape or sanitize dynamic data before injecting into prompts — user input can alter prompt intent (prompt injection attack)
  • Use Java records as parameter objects to keep method signatures clean when many values need binding
  • Store complex prompt templates in src/main/resources/prompts/ files — they're easier to edit and review than multiline Java strings
  • Build the variable map from your domain objects in a dedicated method to keep service logic separate from prompt construction
  • For conditional prompt sections (like refactoredSection), compute the conditional string in Java before passing it to the template
Topics: Java SpringAI
← Newer Post Older Post →