Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Provides REST API design standards and best practices for Spring Boot projects. Use when creating or reviewing REST endpoints, DTOs, error handling, pagination, security headers, HATEOAS and architecture patterns.
.claude/skills/giuseppe-trisciuoglio-spring-boot-rest-api-standards/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-18 | ✓→✗ | ▼ Worse | 442% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 47% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 51% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 76% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 174% | 0% |
REST API design standards for Spring Boot covering URL design, HTTP methods, status codes, DTOs, validation, error handling, pagination, and security headers.
Follow these steps to create well-designed REST API endpoints:
@Data/@Value@Valid annotation on @RequestBody parameters@NotBlank, @Email, @Size, etc.)MethodArgumentNotValidException@RestControllerAdvice for global exception handlingResponseStatusException for specific HTTP status codesValidation checkpoints:
java@RestController @RequestMapping("/v1/users") @RequiredArgsConstructor @Slf4j public class UserController { private final UserService userService; @GetMapping public ResponseEntity<Page<UserResponse>> getAllUsers( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int pageSize) { log.debug("Fetching users page {} size {}", page, pageSize); Page<UserResponse> users = userService.getAll(page, pageSize); return ResponseEntity.ok(users); } @GetMapping("/{id}") public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) { return ResponseEntity.ok(userService.getById(id)); } @PostMapping public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) { UserResponse created = userService.create(request); return ResponseEntity.status(HttpStatus.CREATED).body(created); } @PutMapping("/{id}") public ResponseEntity<UserResponse> updateUser( @PathVariable Long id, @Valid @RequestBody UpdateUserRequest request) { return ResponseEntity.ok(userService.update(id, request)); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.delete(id); return ResponseEntity.noContent().build(); } }
java// Request DTO @Data @NoArgsConstructor @AllArgsConstructor public class CreateUserRequest { @NotBlank(message = "User name cannot be blank") private String name; @Email(message = "Valid email required") private String email; } // Response DTO @Data @NoArgsConstructor @AllArgsConstructor public class UserResponse { private Long id; private String name; private String email; private LocalDateTime createdAt; }
java@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidationException( MethodArgumentNotValidException ex, WebRequest request) { String errors = ex.getBindingResult().getFieldErrors().stream() .map(f -> f.getField() + ": " + f.getDefaultMessage()) .collect(Collectors.joining(", ")); ErrorResponse errorResponse = new ErrorResponse( HttpStatus.BAD_REQUEST.value(), "Validation Error", "Validation failed: " + errors, request.getDescription(false).replaceFirst("uri=", "") ); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); } @ExceptionHandler(ResponseStatusException.class) public ResponseEntity<ErrorResponse> handleResponseStatusException( ResponseStatusException ex, WebRequest request) { ErrorResponse error = new ErrorResponse( ex.getStatusCode().value(), ex.getStatusCode().toString(), ex.getReason(), request.getDescription(false).replaceFirst("uri=", "") ); return new ResponseEntity<>(error, ex.getStatusCode()); } }
java@Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; }
@Value)javapublic record UserResponse(Long id, String name, String email) {}
java@Service @Transactional public class UserService { @Transactional(readOnly = true) public Optional<User> findById(Long id) { return userRepository.findById(id); } @Transactional public User create(User user) { return userRepository.save(user); } }
@RestControllerAdvice, never let raw exceptions bubble upreferences/ directory for comprehensive reference material including HTTP status codes, Spring annotations, and detailed examplesdeveloper-kit-java:spring-boot-code-review-expert agent for code review guidelinesspring-boot-dependency-injection/SKILL.md for dependency injection patterns../spring-boot-test-patterns/SKILL.md for testing REST APIs| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | pass→pass | 14,739 | 11,064 | -25% | 1 | 1 | 0% | 2,253 | 3,304 | +47% | 0 | 0 | — |
case-01 | fail→fail | 19,979 | 14,489 | -27% | 1 | 1 | 0% | 4,369 | 4,675 | +7% | 0 | 0 | — |
case-02 | pass→pass | 9,862 | 7,155 | -27% | 1 | 1 | 0% | 2,079 | 3,149 | +51% | 0 | 0 | — |
case-03 | pass→pass | 7,340 | 3,870 | -47% | 1 | 1 | 0% | 1,318 | 2,326 | +76% | 0 | 0 | — |
case-04 | pass→pass | 4,844 | 2,409 | -50% | 1 | 1 | 0% | 786 | 2,157 | +174% | 0 | 0 | — |
case-05 | pass→pass | 15,544 | 11,074 | -29% | 1 | 1 | 0% | 3,074 | 4,083 | +33% | 0 | 0 | — |
case-07 | pass→pass | 10,541 | 6,958 | -34% | 1 | 1 | 0% | 1,905 | 3,087 | +62% | 0 | 0 | — |
case-08 | pass→pass | 5,011 | 2,796 | -44% | 1 | 1 | 0% | 859 | 2,160 | +151% | 0 | 0 | — |
case-09 | pass→pass | 23,325 | 10,378 | -56% | 1 | 1 | 0% | 2,678 | 3,420 | +28% | 0 | 0 | — |
case-10 | pass→pass | 10,973 | 5,969 | -46% | 1 | 1 | 0% | 1,750 | 2,645 | +51% | 0 | 0 | — |
case-11 | pass→pass | 12,810 | 10,007 | -22% | 1 | 1 | 0% | 2,293 | 3,447 | +50% | 0 | 0 | — |
case-12 | pass→pass | 7,364 | 3,497 | -53% | 1 | 1 | 0% | 1,541 | 2,292 | +49% | 0 | 0 | — |
case-13 | pass→pass | 8,916 | 4,223 | -53% | 1 | 1 | 0% | 1,626 | 2,405 | +48% | 0 | 0 | — |
case-14 | pass→pass | 5,998 | 4,251 | -29% | 1 | 1 | 0% | 972 | 2,577 | +165% | 0 | 0 | — |
case-15 | pass→pass | 16,796 | 12,061 | -28% | 1 | 1 | 0% | 3,324 | 4,428 | +33% | 0 | 0 | — |
case-16 | pass→pass | 6,642 | 4,139 | -38% | 1 | 1 | 0% | 1,294 | 2,438 | +88% | 0 | 0 | — |
case-17 | pass→pass | 13,935 | 10,747 | -23% | 1 | 1 | 0% | 2,765 | 4,074 | +47% | 0 | 0 | — |
case-18 | pass→fail | 2,472 | 2,467 | -0% | 1 | 1 | 0% | 380 | 2,059 | +442% | 0 | 0 | — |
case-19 | pass→pass | 6,187 | 4,268 | -31% | 1 | 1 | 0% | 1,101 | 2,415 | +119% | 0 | 0 | — |
case-20 | pass→pass | 10,542 | 7,470 | -29% | 1 | 1 | 0% | 1,818 | 3,017 | +66% | 0 | 0 | — |
case-21 | pass→pass | 15,296 | 11,187 | -27% | 1 | 1 | 0% | 3,010 | 4,050 | +35% | 0 | 0 | — |
case-22 | fail→fail | 22,477 | 15,298 | -32% | 1 | 1 | 0% | 3,199 | 4,979 | +56% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of -50 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Other measured skills in the registry, with their headline benchmark lift.