You want to plan a trip to Goa. You have five days, a budget of 25,000 rupees, and a clear preference for beaches and seafood. Normally, this means opening ten browser tabs, reading outdated forum posts, and manually cobbling together an itinerary. Instead, imagine sending a single POST request and getting back a structured day-by-day plan with meal suggestions, activity lists, and an exact budget split. That is what this project delivers.

We will build a REST API using Spring Boot and Azure OpenAI. The API accepts a destination, budget, duration, and interests. It returns clean JSON that a frontend or mobile app can render immediately. No scraping. no hardcoded itineraries. Just an AI model prompted to act as a travel planner.

What the API Returns

The response is not a block of Markdown text you have to regex apart. It is a structured JSON object containing daily activities, meal recommendations, and a budget breakdown. For a Goa trip, you might receive a day-one segment that allocates 500 rupees for breakfast at a beach shack, a morning at Palolem, and an evening seafood dinner within a specific locality. Each day carries time slots, estimated costs, and tags like "beach" or "food." This structure matters because modern travel apps do not want to parse paragraphs. They want objects they can map to RecyclerViews or React components.

The Stack and Why It Fits

The project uses Spring Boot 3.5 with Spring AI. Spring AI is the critical piece. It provides a unified ChatModel abstraction so you do not have to write raw HTTP clients against Azure OpenAI. You swap dependencies and properties, not service code.

You need four dependencies in your build file:

  • spring-boot-starter-web for the REST layer.
  • spring-ai-starter-model-azure-openai to connect to the LLM through Spring AI’s interface.
  • springdoc-openapi for automatic Swagger documentation.
  • Lombok to cut down the boilerplate in your request and response POJOs.

Spring AI sits between your business logic and the LLM provider. That positioning is intentional. It keeps your @Service classes clean and provider-agnostic.

Prompt Engineering with PromptTemplates

Hardcoding prompts inside Java strings is a fast way to create unmaintainable software. If the product team decides the AI should sound more casual or refuse budget estimates above a certain threshold, you should not have to recompile your service.

Spring AI provides PromptTemplate. You store the prompt skeleton in a resource file or a dedicated template string, leaving placeholders for variables like {destination}, {budget}, {days}, and {interests}. At runtime, the service creates a Prompt object and injects the user’s values.

Separate system messages from user messages. Use the system message to define the persona. For example, you tell the model it is a travel planner specialized in Indian destinations, budget conscious, and strict about returning only JSON with no markdown fences. Use the user message to pass the specific trip details. This split helps when you later want to A/B test personas without changing the API contract.

The Service Layer: Talking to Azure OpenAI

The @Service class has one job. It builds the prompt, calls the model, cleans the response, and parses the result.

Inject Spring AI’s ChatClient or ChatModel. Render the PromptTemplate with the incoming request values, then call the chat method. The response arrives as a String. Here is where many tutorials stop and real production code starts.

LLMs sometimes add polite preambles. You might get a response that opens with "Here is your itinerary" and then dumps JSON wrapped in triple backticks. If you try to deserialize that directly with Jackson, your app crashes. Add a small helper method that scans the raw string, finds the first opening brace and the last closing brace, and extracts only the JSON payload. Then validate the extracted block. Check that required fields exist and that numeric values make sense before you return the object to the controller.

This defensive parsing is not optional. It is the boundary between a demo and a reliable API.

Handling Errors Like a Mature System

External APIs fail. Azure OpenAI will return rate limit errors, authentication failures, or transient 500s. If you let these bubble up to the user as stack traces, you lose credibility.

Verwenden Sie @RestControllerAdvice, um Ausnahmen global abzufangen. Mappen Sie Spring AI-Exceptions, HttpClientErrorException und generische RuntimeExceptions auf konsistente Fehlerantworten. Geben Sie einen JSON-Body mit einer klaren Nachricht, einem HTTP-Status wie 429 für Rate-Limits und genügend Details zurück, damit der Client den Vorgang wiederholen oder das Problem protokollieren kann. Der Benutzer sollte etwas wie „Service vorübergehend überlastet. Bitte versuchen Sie es in 30 Sekunden erneut“ sehen und keinen Bildschirm voller Java-Klassennamen.

Geheimnisse niemals hartcodieren

Ihr Azure OpenAI API-Key gehört nicht in die in Git eingecheckte application.properties. Externalisieren Sie ihn. Verwenden Sie Umgebungsvariablen, die in Ihrer Spring-Konfiguration referenziert werden, wie zum Beispiel ${AZURE_OPENAI_KEY} und ${AZURE_OPENAI_ENDPOINT}. Behalten Sie eine lokale .env-Datei für die Entwicklung, fügen Sie diese zur .gitignore hinzu und laden Sie sie über das „relaxed binding“ von Spring Boot. Wenn ein Key durchsickert, können Sie ihn an einer einzigen Stelle rotieren, anstatt Ihr gesamtes Artefakt neu zu bauen.

Testen über Swagger

Die springdoc-openapi-Abhängigkeit stellt zur Laufzeit einen Swagger-UI-Endpunkt bereit. Sobald Ihre Anwendung gestartet ist, öffnen Sie /swagger-ui.html in einem Browser. Sie können das Goa-Beispiel direkt ausfüllen: Ziel als „Goa“, Budget als 25000, Tage als 5, Interessen als „beaches, food“. Klicken Sie auf „Execute“ und sehen Sie zu, wie der JSON-Reiseplan erscheint. Dies ermöglicht es Ihnen, Änderungen am Prompt zu validieren, die Serialisierung zu überprüfen und eine Live-Spielwiese mit Frontend-Entwicklern zu teilen, bevor eine der beiden Seiten einen Unit-Test schreibt.

Anbieter wechseln, ohne den Code neu zu schreiben

Startups wechseln Anbieter. Vielleicht laufen die Azure-Guthaben ab, oder Sie möchten die Inferenz gegen eine lokale Ollama-Instanz ausführen, um Kosten zu sparen. Da Spring AI das ChatModel-Interface abstrahiert, ist der Wechsel rein mechanisch. Ändern Sie die Maven-Abhängigkeit von spring-ai-starter-model-azure-openai zu einem anderen Starter, aktualisieren Sie Ihre Properties-Datei mit dem neuen Endpunkt und Key, und lassen Sie Ihre Service-Klasse unberührt. Der von Ihrer mobilen App gesehene API-Vertrag bleibt identisch.

Diese Portabilität macht diese Architektur besonders nützlich für echte Produkte. Sie gehen keine feste Bindung mit Azure ein. Sie nutzen es lediglich als einen Motor, der in eine saubere Spring-Pipeline eingesteckt ist.

Das wichtigste Fazit

Ein KI-Modell ist nicht Ihre Anwendung. Es ist ein externer Dienst, der unvorhersehbare Texte zurückgibt. Behandeln Sie es mit derselben Strenge, mit der Sie ein Payment-Gateway oder eine Wetter-API eines Drittanbieters behandeln würden. Externalisieren Sie Ihre Anmeldedaten. Validieren Sie jede Antwort. Bereinigen Sie die Payload, bevor Sie sie parsen. Behandeln Sie Fehler global, damit Ihre Benutzer niemals einen Stacktrace sehen.

Überlassen Sie der KI die kreative Arbeit, einen Goa-Reiseplan mit einem Budget von 25.000 Rupien zu erstellen. Sie kümmern sich um die technische Basis. Wenn beide getrennt bleiben, erhalten Sie ein System, das tatsächlich produktiv gehen kann.

Die ursprüngliche Anleitung, die diesen Artikel inspiriert hat, finden Sie hier.

Interessiert an einer Diskussion über Spring AI und ähnliche Projekte? Werden Sie Teil der GyaanSetu-Lerncommunity.