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-webfor the REST layer.spring-ai-starter-model-azure-openaito connect to the LLM through Spring AI’s interface.springdoc-openapifor automatic Swagger documentation.Lombokto 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.
Utilisez @RestControllerAdvice pour intercepter les exceptions de manière globale. Mappez les exceptions Spring AI, HttpClientErrorException et les RuntimeException génériques vers des réponses d'erreur cohérentes. Retournez un corps JSON avec un message clair, un statut HTTP comme 429 pour les limites de débit, et suffisamment de détails pour que le client puisse réessayer ou journaliser le problème. L'utilisateur devrait voir quelque chose comme « Service temporairement occupé. Veuillez réessayer dans 30 secondes », et non un écran rempli de noms de classes Java.
Ne codez jamais de secrets en dur
Votre clé API Azure OpenAI ne doit pas se trouver dans le fichier application.properties versionné sur Git. Externalisez-la. Utilisez des variables d'environnement référencées dans votre configuration Spring, telles que ${AZURE_OPENAI_KEY} et ${AZURE_OPENAI_ENDPOINT}. Conservez un fichier .env local pour le développement, ajoutez-le au .gitignore et chargez-le via le "relaxed binding" de Spring Boot. Si une clé est compromise, vous pouvez la renouveler à un seul endroit plutôt que de devoir reconstruire votre artefact.
Tester via Swagger
La dépendance springdoc-openapi expose un point de terminaison Swagger UI lors de l'exécution. Une fois votre application démarrée, ouvrez /swagger-ui.html dans un navigateur. Vous pouvez remplir directement l'exemple de Goa : destination "Goa", budget 25000, jours 5, intérêts "beaches, food". Cliquez sur "execute" et regardez l'itinéraire JSON apparaître. Cela vous permet de valider les changements de prompts, de vérifier la sérialisation et de partager un environnement de test interactif avec les développeurs frontend avant que l'une ou l'autre des parties n'écrive un test unitaire.
Changer de fournisseur sans réécrire de code
Les startups changent de fournisseur. Peut-être que les crédits Azure expirent, ou que vous souhaitez exécuter l'inférence sur une instance Ollama locale pour réduire les coûts. Comme Spring AI abstrait l'interface ChatModel, le changement est purement mécanique. Modifiez la dépendance Maven de spring-ai-starter-model-azure-openai vers un autre starter, mettez à jour votre fichier de propriétés avec le nouvel endpoint et la nouvelle clé, et ne touchez pas à votre classe de service. Le contrat API vu par votre application mobile reste identique.
Cette portabilité rend cette architecture particulièrement utile pour des produits réels. Vous ne vous mariez pas avec Azure. Vous l'utilisez comme un moteur branché sur un pipeline Spring propre.
Ce qu'il faut vraiment retenir
Un modèle d'IA n'est pas votre application. C'est un service externe qui renvoie du texte imprévisible. Traitez-le avec la même rigueur que vous accorderiez à une passerelle de paiement ou à une API météo tierce. Externalisez vos identifiants. Validez chaque réponse. Nettoyez la charge utile avant de l'analyser. Gérez les erreurs de manière globale pour que vos utilisateurs ne voient jamais de trace de pile.
Laissez l'IA s'occuper du travail créatif de construction d'un itinéraire pour Goa avec un budget de 25 000 roupies. Vous, occupez-vous de la tuyauterie. Lorsque les deux restent séparés, vous obtenez un système qui est réellement prêt pour la production.
Le tutoriel original qui a inspiré cet article est disponible ici.
Intéressé par la discussion sur Spring AI et des projets similaires ? Rejoignez la communauté d'apprentissage GyaanSetu.
