This blog post will integrate the Spring AI framework into a Java application. We’ll use a simple project that includes a ChatService and a ChatController to demonstrate using the Spring AI framework to generate text & image responses and horoscopes based on user input.
Update 12/28/2025 – Updated from M1 release to Spring AI 1.1.0 GA
Prerequisites
Before we start, make sure you have the following:
- Java Development Kit (JDK) installed
- Familiarity with Spring Framework
- Basic understanding of RESTful APIs
- An OpenAI API key
Obtaining an OpenAI API Key
To use the OpenAI API, you’ll need to sign up for an API key:
- Go to the OpenAI website.
- Sign up for an account if you don’t already have one.
- Navigate to the API section and generate a new API key.
Once you have your API key, set the OS environment variable OPEN_API_KEY to this value. We will refer to it in the spring application.properties file
Setting Up the Project
First, let’s set up our project. Create a new Spring Boot project and add the necessary dependencies for Spring AI.
Maven Dependencies
Add the following dependencies to your pom.xml:
|
1 2 3 4 5 6 7 8 9 10 11 |
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> <!-- Add other necessary dependencies here --> </dependencies> |
Configuring the API Key
Point to the OpenAI API key in the application.properties file. Please make sure to set the environment variable OPENAI_API_KEY to the API key. Avoid hardcoding the API key directly in the properties file.
|
1 |
spring.ai.openai.api-key=${OPENAI_API_KEY} |
Implementing the ChatService
The ChatService class contains methods to process AI prompts and generate horoscopes. Here’s the complete implementation:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
/** * Service class for handling AI chat and image generation operations. Provides * methods to interact with OpenAI's chat models and image generation * capabilities. */ @Service public class ChatService { private static final Logger logger = LoggerFactory.getLogger(ChatService.class); @Autowired private ChatClient chatClient; @Autowired private ImageModel imageModel; /** * Processes a user prompt using the specified GPT model and temperature * setting. * * @param prompt the user's input prompt to be processed by the AI model * @param gptModelName the name of the GPT model to use (e.g., "gpt-5.2", * "gpt-4") * @param temperature controls randomness in the response (0.0 = * deterministic, 2.0 = very random) * @return the AI-generated response content * @throws IllegalArgumentException if the prompt is null, empty, too long, or contains invalid characters */ public String processPrompt(String prompt, String gptModelName, double temperature) { logger.info("Processing prompt request - model: {}, temperature: {}, promptLength: {}", gptModelName, temperature, prompt != null ? prompt.length() : 0); try { validatePrompt(prompt, "Prompt"); validateModelName(gptModelName); validateTemperature(temperature); String response = chatClient.prompt(new Prompt( prompt, OpenAiChatOptions.builder() .model(gptModelName) .temperature(temperature) .build() )) .call() .content(); logger.info("Successfully processed prompt - responseLength: {}", response != null ? response.length() : 0); return response; } catch (IllegalArgumentException e) { logger.warn("Validation failed for prompt request: {}", e.getMessage()); throw e; } catch (Exception e) { logger.error("Error processing prompt with model {}: {}", gptModelName, e.getMessage(), e); throw new RuntimeException("Failed to process prompt", e); } } /** * Generates a personalized horoscope prediction based on zodiac sign and * number of days. Uses a fortune-telling prompt template to create * predictions. * * @param zodiacSign the zodiac sign for the horoscope (e.g., "Aries", * "Taurus") * @param days the number of days to predict into the future * @param gptModelName the name of the GPT model to use (e.g., "gpt-5.2", * "gpt-4") * @param temperature controls randomness in the response (0.0 = * deterministic, 2.0 = very random) * @return the AI-generated horoscope prediction * @throws IllegalArgumentException if validation fails on any parameter */ public String getMyHoroscope(String zodiacSign, int days, String gptModelName, double temperature) { logger.info("Processing horoscope request - zodiacSign: {}, days: {}, model: {}, temperature: {}", zodiacSign, days, gptModelName, temperature); try { // Validate all inputs validateZodiacSign(zodiacSign); validateDays(days); validateModelName(gptModelName); validateTemperature(temperature); // Sanitize the zodiac sign input to prevent prompt injection String sanitizedZodiacSign = sanitizeInput(zodiacSign); logger.debug("Sanitized zodiac sign from '{}' to '{}'", zodiacSign, sanitizedZodiacSign); String template = "I am a fortune teller. I can predict your future. Given your zodiac sign {zodiacSign} please tell me my future for the next {days} days?"; PromptTemplate promptTemplate = new PromptTemplate(template); Map<String, Object> params = Map.of( "zodiacSign", sanitizedZodiacSign, "days", days ); String response = chatClient.prompt(new Prompt( promptTemplate.render(params), OpenAiChatOptions.builder() .model(gptModelName) .temperature(temperature) .build() )) .call() .content(); logger.info("Successfully generated horoscope for {} ({} days) - responseLength: {}", sanitizedZodiacSign, days, response != null ? response.length() : 0); return response; } catch (IllegalArgumentException e) { logger.warn("Validation failed for horoscope request: {}", e.getMessage()); throw e; } catch (Exception e) { logger.error("Error generating horoscope for {} with model {}: {}", zodiacSign, gptModelName, e.getMessage(), e); throw new RuntimeException("Failed to generate horoscope", e); } } /** * Generates an image using DALL-E 3 based on the provided text prompt. The * image is generated with standard quality at 1024x1024 resolution. * * @param imagePrompt the text description of the image to generate * @return the URL of the generated image * @throws IllegalArgumentException if the image prompt is null, empty, or too long */ public String generateImage(String imagePrompt) { logger.info("Processing image generation request - promptLength: {}", imagePrompt != null ? imagePrompt.length() : 0); try { validatePrompt(imagePrompt, "Image prompt"); String imageUrl = imageModel.call( new ImagePrompt(imagePrompt, OpenAiImageOptions.builder() .quality("standard") .model("dall-e-3") .N(1) .height(1024) .width(1024).build()) ).getResult().getOutput().getUrl(); logger.info("Successfully generated image - URL: {}", imageUrl); return imageUrl; } catch (IllegalArgumentException e) { logger.warn("Validation failed for image generation request: {}", e.getMessage()); throw e; } catch (Exception e) { logger.error("Error generating image: {}", e.getMessage(), e); throw new RuntimeException("Failed to generate image", e); } } /// rest of code removed ... see git repo for full code } |
processPrompt: This method takes a prompt, model name, and temperature as parameters and returns the AI-generated text response.getMyHoroscope: This method generates a horoscope based on the user’s zodiac sign and the specified number of days for which you want to know your future. Note: I do not have to say this, but I will — this is a generic response and nothing to do with your real life or what you should expect in the future (just saying!)lgenerateImage: This method generates an image based on the provided message. It returns the URL where you can view the generated image.
Implementing the ChatController
The ChatController class exposes REST endpoints to interact with the ChatService. Here’s the complete implementation:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
/** * REST controller for AI-powered chat and image generation endpoints. * Provides API endpoints for chat interactions, horoscope predictions, and image generation. */ @RestController @RequestMapping("/api") public class ChatController { private static final Logger logger = LoggerFactory.getLogger(ChatController.class); @Autowired private ChatService chatService; @Autowired private RagService ragService; @Value("${spring.ai.openai.model:gpt-4o}") private String defaultModelName; @Value("${spring.ai.openai.temperature:0.7}") private double defaultTemperature; /** * Processes a chat prompt and returns an AI-generated response. * Uses model and temperature settings from application.properties. * * @param request the chat request containing the user's prompt * @return ResponseEntity with the AI response or error message */ @PostMapping("/chat") public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request) { logger.info("Received chat request - promptLength: {}, model: {}, temperature: {}", request.prompt() != null ? request.prompt().length() : 0, defaultModelName, defaultTemperature); try { String response = chatService.processPrompt(request.prompt(), defaultModelName, defaultTemperature); logger.info("Chat request processed successfully"); return ResponseEntity.ok(new ChatResponse(response)); } catch (IllegalArgumentException e) { logger.warn("Chat request validation failed: {}", e.getMessage()); return ResponseEntity.badRequest() .body(new ChatResponse(e.getMessage())); } catch (Exception e) { logger.error("Unexpected error processing chat request: {}", e.getMessage(), e); return ResponseEntity.internalServerError() .body(new ChatResponse("An unexpected error occurred")); } } /** * Generates an image based on a text prompt using DALL-E 3. * * @param message the image generation prompt * @return the URL of the generated image */ @GetMapping("/image/gen") public String generateImage(@RequestParam String message) { logger.info("Received image generation request - messageLength: {}", message != null ? message.length() : 0); try { String imageUrl = chatService.generateImage(message); logger.info("Image generated successfully"); return imageUrl; } catch (IllegalArgumentException e) { logger.warn("Image generation validation failed: {}", e.getMessage()); throw e; } catch (Exception e) { logger.error("Unexpected error generating image: {}", e.getMessage(), e); throw new RuntimeException("Failed to generate image", e); } } /** * Generates a personalized horoscope prediction. * Uses model and temperature settings from application.properties. * * @param request the horoscope request containing zodiac sign and number of days * @return the AI-generated horoscope prediction */ @PostMapping("/horoscope") public String getMyHoroscope(@RequestBody HoroscopeRequest request) { logger.info("Received horoscope request - zodiacSign: {}, days: {}, model: {}, temperature: {}", request.zodiacSign(), request.days(), defaultModelName, defaultTemperature); try { String horoscope = chatService.getMyHoroscope(request.zodiacSign(), request.days(), defaultModelName, defaultTemperature); logger.info("Horoscope generated successfully for {}", request.zodiacSign()); return horoscope; } catch (IllegalArgumentException e) { logger.warn("Horoscope request validation failed: {}", e.getMessage()); throw e; } catch (Exception e) { logger.error("Unexpected error generating horoscope: {}", e.getMessage(), e); throw new RuntimeException("Failed to generate horoscope", e); } } /// rest of code removed ... see git repo for full code } |
Running the Application
To run the application, use the following command line from your Mac OS terminal.
|
1 2 3 |
./mvnw clean spring-boot:run curl -X POST -H "Content-Type: application/json" http://localhost:8080/api/chat -d '{"prompt": "who are you?"}' |
I have included a Postman collection in the Git project repository.
Conclusion
In this blog post, we demonstrated how to integrate the Spring AI framework into a Java application. We created a ChatService to handle AI prompts and horoscope generation, and exposed REST endpoints through a ChatController. This should give you a good starting point for building more advanced AI-powered Spring AI applications. The code is at https://github.com/thomasma/spring-ai