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.
@RestControllerAdvice का उपयोग करके अपवादों (exceptions) को वैश्विक स्तर पर इंटरसेप्ट करें। Spring AI exceptions, HttpClientErrorException, और generic RuntimeExceptions को सुसंगत (consistent) एरर रिस्पॉन्स में मैप करें। एक स्पष्ट संदेश, रेट लिमिट के लिए 429 जैसा HTTP स्टेटस, और क्लाइंट के लिए समस्या को दोबारा प्रयास करने या लॉग करने के लिए पर्याप्त विवरण के साथ एक JSON बॉडी लौटाएं। उपयोगकर्ता को "Service temporarily busy. Please retry in 30 seconds" जैसा कुछ दिखना चाहिए, न कि जावा क्लास नामों से भरी हुई स्क्रीन।
कभी भी सीक्रेट्स को हार्डकोड न करें
आपकी Azure OpenAI API key को Git में चेक किए गए application.properties में नहीं होना चाहिए। इसे एक्सटर्नलाइज़ (externalize) करें। अपने Spring कॉन्फ़िगरेशन में संदर्भित एनवायरनमेंट वेरिएबल्स का उपयोग करें, जैसे कि ${AZURE_OPENAI_KEY} और ${AZURE_OPENAI_ENDPOINT}। डेवलपमेंट के लिए एक लोकल .env फ़ाइल रखें, इसे .gitignore में जोड़ें, और इसे Spring Boot के relaxed binding के माध्यम से लोड करें। यदि कोई की (key) लीक हो जाती है, तो आप अपने आर्टिफैक्ट को फिर से बनाने के बजाय उसे एक ही स्थान पर बदल (rotate) सकते हैं।
Swagger के माध्यम से टेस्टिंग
springdoc-openapi डिपेंडेंसी रनटाइम पर एक Swagger UI एंडपॉइंट एक्सपोज़ करती है। एक बार जब आपका एप्लिकेशन शुरू हो जाए, तो ब्राउज़र में /swagger-ui.html खोलें। आप गोवा (Goa) का उदाहरण सीधे भर सकते हैं: डेस्टिनेशन "Goa," बजट 25000, दिन 5, और रुचियां (interests) "beaches, food" के रूप में। execute दबाएं और JSON इटिनररी (itinerary) को सामने आते हुए देखें। यह आपको प्रॉम्प्ट परिवर्तनों को मान्य करने, सीरियलाइजेशन (serialization) को सत्यापित करने और यूनिट टेस्ट लिखने से पहले फ्रंटएंड डेवलपर्स के साथ एक लाइव प्लेग्राउंड साझा करने की अनुमति देता है।
कोड को दोबारा लिखे बिना प्रोवाइडर्स को बदलना
स्टार्टअप्स प्रोवाइडर्स बदलते रहते हैं। हो सकता है कि Azure क्रेडिट समाप्त हो जाएं, या लागत कम करने के लिए आप एक लोकल Ollama इंस्टेंस के खिलाफ इन्फरेंस (inference) चलाना चाहें। चूंकि Spring AI ChatModel इंटरफ़ेस को एब्स्ट्रैक्ट (abstract) करता है, इसलिए इसे बदलना बहुत आसान (mechanical) है। Maven डिपेंडेंसी को spring-ai-starter-model-azure-openai से बदलकर किसी अन्य स्टार्टर में बदलें, अपनी प्रॉपर्टीज़ फ़ाइल को नए एंडपॉइंट और की (key) के साथ अपडेट करें, और अपनी सर्विस क्लास को वैसा ही रहने दें। आपके मोबाइल ऐप द्वारा देखा जाने वाला API कॉन्ट्रैक्ट बिल्कुल वैसा ही रहता है।
वह पोर्टेबिलिटी इस आर्किटेक्चर को वास्तविक उत्पादों के लिए विशेष रूप से उपयोगी बनाती है। आप Azure के साथ बंधे नहीं हैं। आप इसे एक साफ-सुथरे Spring पाइपलाइन में प्लग किए गए एक इंजन के रूप में उपयोग कर रहे हैं।
मुख्य निष्कर्ष (The Real Takeaway)
एक AI मॉडल आपका एप्लिकेशन नहीं है। यह एक बाहरी सेवा है जो अप्रत्याशित टेक्स्ट लौटाती है। इसके साथ उसी सख्ती से व्यवहार करें जैसा आप किसी पेमेंट गेटवे या थर्ड-पार्टी वेदर API के साथ करेंगे। अपने क्रेडेंशियल्स को एक्सटर्नलाइज़ करें। प्रत्येक रिस्पॉन्स को मान्य (validate) करें। पार्सिंग से पहले पेलोड को साफ़ करें। एरर्स को वैश्विक स्तर पर संभालें ताकि आपके उपयोगकर्ता कभी भी स्टैक ट्रेस (stack trace) न देखें।
AI को 25,000 रुपये के बजट पर गोवा इटिनररी बनाने का रचनात्मक काम करने दें। आप प्लंबिंग (infrastructure) संभालें। जब ये दोनों अलग रहते हैं, तो आपको एक ऐसा सिस्टम मिलता है जो वास्तव में काम करता है (ships)।
इस लेख को प्रेरित करने वाला मूल वॉकथ्रू यहाँ पाया जा सकता है।
Spring AI और इसी तरह की परियोजनाओं पर चर्चा करने में रुचि रखते हैं? GyaanSetu learning community में शामिल हों।
