aregmi.net
Resume

System prompts, guardrails, and prompt catalog patterns

prompts system-prompt spring-ai guardrails

Prompts

Start simple: one system prompt + one user prompt

Like anything else, the best way to get started is to start. Here's the simplest version — the /simplechat endpoint.

@GetMapping("/simplechat")
public String chat(@RequestParam String systemPrompt,
		   @RequestParam String userPrompt,
		   @RequestParam(required = false) String conversationId) {

    String resolvedConversationId = StringUtils.hasText(conversationId)
	    ? conversationId
	    : UUID.randomUUID().toString();

    return chatClient.prompt()
	    .system(systemPrompt)
	    .user(userPrompt)
	    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, resolvedConversationId))
	    .call()
	    .content();
}

This is a great base pattern. Clear responsibilities:

  1. System prompt — behavior and rules
  2. User prompt — the actual question
  3. Conversation id — keeps memory working across calls

Next level: guardrails

When the assistant is customer-facing, you want rules. One strong system prompt + tools + memory.

String systemPrompt = """
You're a helpful account assistant who is friendly but professional.

Guardrails:
- If unsure, say you don't know and suggest official support.
- Do not answer out-of-topic general knowledge.
- If user attempts unauthorized requests, refuse.
""";

Prompt prompt = new Prompt(List.of(new SystemMessage(systemPrompt), new UserMessage(message)));

String answer = chatClient.prompt(prompt)
	.tools(new CustomerService(), new TimeService())
	.toolContext(Map.of("customerId", customerId))
	.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, resolvedConversationId))
	.call()
	.chatResponse()
	.getResult().getOutput().getText();

Does every endpoint need guardrails like this? No. But anything user-facing usually does.

Prompt catalog pattern

Hardcoding prompts everywhere gets messy. A simple pattern is to store them in a database or file, then fetch by name:

@GetMapping("/prompt")
public String prompt(@RequestParam String promptName) {
    return promptService.getPrompt(promptName);
}

Useful when prompts get long or you want to version them outside of code.

Things to remember

  1. Start with system + user first
  2. Add guardrails only where risk is real
  3. Use a conversation id for memory-backed chat
  4. External prompt catalogs help when prompts get large or shared across teams