From spring-boot-plugin
Spring Boot coding conventions: REST controller design, service layer, dependency injection, configuration, DTO patterns, exception handling, and Bean Validation. Apply on any Spring Boot project. Works alongside spring-boot-plugin:spring-data-jpa for persistence and java-foundation:java-conventions for domain objects. Use this skill to: - Structure REST endpoints with @RestController, proper HTTP semantics, and DTO mapping. - Enforce constructor injection and @ConfigurationProperties over @Value on fields. - Write @Service classes with transactional boundaries at the right layer. - Handle errors consistently with @RestControllerAdvice and ProblemDetail (RFC 9457). Do NOT use this skill for: - JPA entity and repository patterns — see spring-boot-plugin:spring-data-jpa. - Spring Security configuration — see the security phase injection in stack.md. - Build tool configuration — see java-foundation:build-tooling.
How this skill is triggered — by the user, by Claude, or both
Slash command
/spring-boot-plugin:spring-conventionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```java
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor // Lombok — generates constructor for final fields
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public UserResponse getById(@PathVariable Long id) {
return userService.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User", id));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResponse create(@RequestBody @Valid CreateUserRequest request) {
return userService.create(request);
}
@PutMapping("/{id}")
public UserResponse update(@PathVariable Long id,
@RequestBody @Valid UpdateUserRequest request) {
return userService.update(id, request);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
userService.delete(id);
}
}
Return DTO directly (not ResponseEntity<T>) unless you need explicit control over headers or status codes beyond @ResponseStatus. ResponseEntity is valid for partial-update responses (202 Accepted + Location header) or conditional-GET (ETag / If-None-Match).
HTTP semantics:
GET — idempotent, no body.POST — create, return 201 Created + new resource.PUT — replace entire resource, return 200 OK with updated resource.PATCH — partial update.DELETE — return 204 No Content.// Request DTO — mutable, with Bean Validation
public record CreateUserRequest(
@NotBlank @Email String email,
@NotBlank @Size(min = 8) String password,
@NotNull Role role
) {}
// Response DTO — immutable record, projection of domain data
public record UserResponse(
Long id,
String email,
Role role,
Instant createdAt
) {
public static UserResponse from(User user) {
return new UserResponse(user.id(), user.getEmail(), user.getRole(), user.getCreatedAt());
}
}
Separate request and response types. Never expose the JPA entity directly from the controller — it leaks schema details and creates bidirectional serialization issues.
Use static factory methods (from(Entity)) on response records for clean mapping without a mapper library when the mapping is trivial. For complex mappings with many fields, introduce a UserMapper interface and implement with MapStruct or plain Java.
@Service
@Transactional(readOnly = true) // Default to read-only; override on write methods
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public Optional<UserResponse> findById(Long id) {
return userRepository.findById(id).map(UserResponse::from);
}
@Transactional
public UserResponse create(CreateUserRequest request) {
if (userRepository.existsByEmail(request.email())) {
throw new DuplicateEmailException(request.email());
}
var user = new User(request.email(), passwordEncoder.encode(request.password()), request.role());
return UserResponse.from(userRepository.save(user));
}
@Transactional
public void delete(Long id) {
var user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User", id));
userRepository.delete(user);
}
}
@Transactional(readOnly = true) on the class sets a safe default; @Transactional on write methods overrides it. Avoid @Transactional on controllers — it spans too wide a scope and complicates error handling.
// Prefer (with Lombok @RequiredArgsConstructor or explicit constructor)
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final InvoiceService invoiceService;
public OrderService(OrderRepository orderRepository, InvoiceService invoiceService) {
this.orderRepository = orderRepository;
this.invoiceService = invoiceService;
}
}
// Avoid — field injection hides dependencies and makes testing harder
@Service
public class OrderService {
@Autowired private OrderRepository orderRepository; // DON'T
}
// application.yml
// app:
// mail:
// host: smtp.example.com
// port: 587
// from: [email protected]
@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(String host, int port, String from) {}
// Register it
@SpringBootApplication
@EnableConfigurationProperties(MailProperties.class)
public class Application { ... }
// Inject like any bean
@Service
@RequiredArgsConstructor
public class MailService {
private final MailProperties mailProperties;
}
Use @ConfigurationProperties records for any group of related configuration values. Avoid @Value on fields for groups — it scatters configuration and makes testing harder.
public record CreateProductRequest(
@NotBlank(message = "name is required") String name,
@NotNull @Positive BigDecimal price,
@NotNull @Min(0) Integer stock,
@NotBlank @Pattern(regexp = "[A-Z]{3}") String currencyCode
) {}
Activate in controllers with @Valid on @RequestBody / @PathVariable / @ModelAttribute. Spring Boot auto-configures MethodArgumentNotValidException handling — provide a @RestControllerAdvice to shape the error response.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ProblemDetail handleNotFound(EntityNotFoundException ex) {
var problem = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
problem.setTitle("Resource Not Found");
return problem;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
var errors = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.toList();
var problem = ProblemDetail.forStatusAndDetail(
HttpStatus.UNPROCESSABLE_ENTITY, "Validation failed");
problem.setProperty("errors", errors);
return problem;
}
}
Use ProblemDetail (Spring Boot 3.x, RFC 9457) for structured error responses. For Spring Boot 2.x, return a custom ErrorResponse record.
public class EntityNotFoundException extends RuntimeException {
public EntityNotFoundException(String entityName, Object id) {
super(entityName + " not found: " + id);
}
}
public class DuplicateEmailException extends RuntimeException {
public DuplicateEmailException(String email) {
super("Email already registered: " + email);
}
}
Keep domain exceptions in a dedicated package (exception or error). Do not catch and re-throw RuntimeException unless translating (e.g., wrapping a JPA exception into a domain exception).
npx claudepluginhub aratkruglik/claude-sdlc --plugin spring-boot-pluginProvides REST API standards for Spring Boot covering URL design, HTTP methods, DTOs, validation, error handling, pagination, and security headers. Use when creating or reviewing endpoints.
Provides Spring Boot patterns for REST API design, layered architecture (Controller-Service-Repository), Spring Data JPA repositories, transactional services, DTO validation, and global exception handling. Useful for scalable Java backends.
Generates Spring Boot 3.x configurations, REST controllers, Spring Security 6 auth, Spring Data JPA repositories, and WebFlux endpoints. Use for microservices, reactive Java apps, or Spring Cloud integration.