Spring AI Code Generation — Build an AI-Powered Code Assistant in Spring Boot
Spring AI can generate, review, refactor, and explain Java code programmatically. By combining structured output, streaming, and tool calling, you can build sophisticated code assistants that integrate with your CI/CD pipeline, IDE plugins, or developer portal.
Use Cases for AI Code Generation
Use Case Input Output
──────────────────────────────────────────────────────────────────────
Generate REST endpoint Entity class Controller + Service + Repository
Write unit tests Method signature JUnit 5 test class
Explain legacy code Java method body Plain English explanation
Refactor for patterns Old-style code Modern Java (records, streams)
Generate boilerplate Database schema JPA entities
SQL to JPQL conversion SQL query string JPQL equivalent
Code Generation Records
public record GeneratedCode(
String className,
String packageName,
String code,
List<String> imports,
String explanation
) {}
public record TestSuite(
String testClassName,
String code,
List<String> testMethodNames,
int estimatedCoveragePercent
) {}
Code Generation Service
@Service
public class CodeGenerationService {
private final ChatClient chatClient;
public CodeGenerationService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("""
You are an expert Java 17+ and Spring Boot 3.3 developer.
Generate clean, production-ready code with:
- Proper annotations
- Null safety
- No unnecessary boilerplate
- Modern Java idioms (records, var, switch expressions, text blocks)
""")
.build();
}
// Generate a complete REST controller from a domain description
public GeneratedCode generateRestController(String entityName, String operations) {
return chatClient.prompt()
.user("""
Generate a Spring Boot REST controller for entity: %s
Operations needed: %s
Requirements:
- Use @RestController, @RequestMapping
- Constructor injection
- ResponseEntity return types
- Validation annotations on parameters
- Proper HTTP methods (GET/POST/PUT/DELETE)
""".formatted(entityName, operations))
.call()
.entity(GeneratedCode.class);
}
// Generate unit tests for existing code
public TestSuite generateTests(String className, String methodCode) {
return chatClient.prompt()
.user("""
Generate a comprehensive JUnit 5 test class for this Java method:
Class: %s
Method:
%s
Requirements:
- Use @ExtendWith(MockitoExtension.class)
- Test happy path, null inputs, edge cases
- Use assertThat from AssertJ
- Descriptive test method names (snake_case)
""".formatted(className, methodCode))
.call()
.entity(TestSuite.class);
}
// Stream code generation for long outputs
public Flux<String> streamCodeGeneration(String prompt) {
return chatClient.prompt()
.user(prompt)
.stream()
.content();
}
}
Code Review Service
public record CodeReview(
String overallAssessment,
List<Issue> issues,
List<String> suggestions,
int qualityScore, // 0-100
String refactoredCode // improved version
) {}
public record Issue(
String severity, // "critical", "warning", "info"
int lineNumber,
String description,
String fixSuggestion
) {}
@Service
public class CodeReviewService {
private final ChatClient chatClient;
public CodeReviewService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultOptions(OpenAiChatOptions.builder()
.withModel("gpt-4o") // use strongest model for code review
.withTemperature(0.1f)
.build())
.build();
}
public CodeReview reviewCode(String javaCode) {
return chatClient.prompt()
.system("""
Review Java code for:
1. Bugs and potential NPEs
2. Security vulnerabilities (SQL injection, XSS, etc.)
3. Performance issues (N+1, unnecessary loops)
4. Code style and best practices
Provide a quality score 0-100 and a refactored version.
""")
.user("Review this code:\n\n" + javaCode)
.call()
.entity(CodeReview.class);
}
}
Code Explanation Service
@Service
public class CodeExplainerService {
private final ChatClient chatClient;
public CodeExplainerService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String explainCode(String javaCode, String audienceLevel) {
return chatClient.prompt()
.system("Explain Java code for a " + audienceLevel + " developer. " +
"Be clear and use examples where helpful.")
.user("Explain this code:\n\n" + javaCode)
.call()
.content();
}
public String explainMethod(String methodSignature, String methodBody) {
return chatClient.prompt()
.user("""
Explain what this Java method does in plain English:
Signature: %s
Body:
%s
Include: purpose, parameters, return value, side effects.
""".formatted(methodSignature, methodBody))
.call()
.content();
}
}
REST Controller for Code Assistant
@RestController
@RequestMapping("/code-assistant")
public class CodeAssistantController {
private final CodeGenerationService generator;
private final CodeReviewService reviewer;
private final CodeExplainerService explainer;
@PostMapping("/generate/controller")
public GeneratedCode generateController(@RequestBody GenerateRequest req) {
return generator.generateRestController(req.entityName(), req.operations());
}
@PostMapping("/generate/tests")
public TestSuite generateTests(@RequestBody TestGenRequest req) {
return generator.generateTests(req.className(), req.methodCode());
}
@PostMapping("/review")
public CodeReview review(@RequestBody CodeRequest req) {
return reviewer.reviewCode(req.code());
}
@PostMapping("/explain")
public Map<String, String> explain(@RequestBody ExplainRequest req) {
String explanation = explainer.explainCode(req.code(), req.level());
return Map.of("explanation", explanation);
}
@PostMapping(value = "/generate/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> generateStream(@RequestBody GenerateStreamRequest req) {
return generator.streamCodeGeneration(req.prompt());
}
}
Output
// POST /code-assistant/generate/controller
{
"entityName": "Product",
"operations": "CRUD + search by name"
}
Response:
{
"className": "ProductController",
"packageName": "com.example.controller",
"code": "@RestController\n@RequestMapping(\"/products\")\npublic class ProductController {\n private final ProductService service;\n public ProductController(ProductService service) { this.service = service; }\n @GetMapping public List findAll() { return service.findAll(); }\n @GetMapping(\"/{id}\") public ResponseEntity findById(@PathVariable Long id) { return ResponseEntity.ok(service.findById(id)); }\n @PostMapping public ResponseEntity create(@Valid @RequestBody Product p) { return ResponseEntity.status(201).body(service.save(p)); }\n @GetMapping(\"/search\") public List search(@RequestParam String name) { return service.searchByName(name); }\n}",
"imports": ["org.springframework.web.bind.annotation.*", "org.springframework.http.ResponseEntity", "java.util.List"],
"explanation": "REST controller with CRUD endpoints and name-based search"
}
Key Points
- Use
temperature=0.1for code generation — slightly above zero to allow stylistic variation while keeping logic deterministic - GPT-4o produces significantly better code than GPT-4o-mini for complex generation tasks — use it for review and generation, cheaper models for explanation
- Always validate generated code syntactically before exposing it to users — wrap the output in a compilation check using the Java Compiler API or `javax.tools`
- Stream long code generation with
text/event-stream— displaying code character by character feels much faster than a 10-second wait for the full output - For production code assistants, add a second review pass: generate code → review generated code → show both to the developer
Comments