Spring Boot REST API Standards
Overview
REST API design standards for Spring Boot covering URL design, HTTP methods, status codes, DTOs, validation, error handling, pagination, and security headers.
When to Use
- Creating REST endpoints and API routes
- Designing DTOs and API contracts
- Implementing error handling and validation
- Setting up pagination and filtering
- Configuring security headers and CORS
- Reviewing REST API architecture
Instructions
To Build RESTful API Endpoints
Follow these steps to create well-designed REST API endpoints:
Design Resource-Based URLs
- Use plural nouns for resource names
- Follow REST conventions: GET /users, POST /users, PUT /users/{id}
- Avoid action-based URLs like /getUserList
Implement Proper HTTP Methods
- GET: Retrieve resources (safe, idempotent)
- POST: Create resources (not idempotent)
- PUT: Replace entire resources (idempotent)
- PATCH: Partial updates (not idempotent)
- DELETE: Remove resources (idempotent)
Use Appropriate Status Codes
- 200 OK: Successful GET/PUT/PATCH
- 201 Created: Successful POST with Location header
- 204 No Content: Successful DELETE
- 400 Bad Request: Invalid request data
- 404 Not Found: Resource doesn't exist
- 409 Conflict: Duplicate resource
- 500 Internal Server Error: Unexpected errors
Create Request/Response DTOs
- Separate API contracts from domain entities
- Use Java records or Lombok
@Data/@Value
- Apply Jakarta validation annotations
- Keep DTOs immutable when possible
Implement Validation
- Use
@Valid annotation on @RequestBody parameters
- Apply validation constraints (
@NotBlank, @Email, @Size, etc.)
- Handle validation errors with
MethodArgumentNotValidException
Set Up Error Handling
- Use
@RestControllerAdvice for global exception handling
- Return standardized error responses with status, error, message, and timestamp
- Use
ResponseStatusException for specific HTTP status codes
Configure Pagination
- Use Pageable for large datasets
- Include page, size, sort parameters
- Return metadata with total elements, totalPages, etc.
Add Security Headers
- Configure CORS policies
- Set content security policy
- Include X-Frame-Options, X-Content-Type-Options
Validation checkpoints:
- After step 1-2: Verify URL structure follows REST conventions (/users not /getUsers)
- After step 3: Test each endpoint returns correct status codes
- After step 4-5: Validate DTOs with curl or HTTPie before proceeding
- After step 6: Confirm error responses match standardized format
Examples
Basic CRUD Controller
@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();
}
}
Request/Response DTOs
// 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;
}
Global Exception Handler
@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());
}
}
Best Practices
1. Use Constructor Injection
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
}
2. Prefer Immutable DTOs (Java Records or @Value)
public record UserResponse(Long id, String name, String email) {}
3. Implement Proper Transaction Management
@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); }
}
Constraints and Warnings
- Never expose entities directly - Use DTOs to separate API contracts from domain models
- Follow REST conventions - Use nouns for resources (/users), correct HTTP methods, plural names, proper status codes
- Handle all exceptions globally - Use
@RestControllerAdvice, never let raw exceptions bubble up
- Always paginate large result sets - Prevent performance issues and DDoS vulnerabilities
- Validate all input data - Use Jakarta validation annotations on request DTOs
- Never expose sensitive data - Don't log or expose passwords, tokens, PII
References
- See
references/ directory for comprehensive reference material including HTTP status codes, Spring annotations, and detailed examples
- Refer to the
developer-kit-java:spring-boot-code-review-expert agent for code review guidelines
- Review
spring-boot-dependency-injection/SKILL.md for dependency injection patterns
- Check
../spring-boot-test-patterns/SKILL.md for testing REST APIs
1---2name: spring-boot-rest-api-standards3description: 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.4---5
6# Spring Boot REST API Standards
7
8## Overview
9
10REST API design standards for Spring Boot covering URL design, HTTP methods, status codes, DTOs, validation, error handling, pagination, and security headers.
11
12## When to Use
13
14- Creating REST endpoints and API routes
15- Designing DTOs and API contracts
16- Implementing error handling and validation
17- Setting up pagination and filtering
18- Configuring security headers and CORS
19- Reviewing REST API architecture
20
21## Instructions
22
23### To Build RESTful API Endpoints
24
25Follow these steps to create well-designed REST API endpoints:
26
271. **Design Resource-Based URLs**
28 - Use plural nouns for resource names
29 - Follow REST conventions: GET /users, POST /users, PUT /users/{id}
30 - Avoid action-based URLs like /getUserList
31
322. **Implement Proper HTTP Methods**
33 - GET: Retrieve resources (safe, idempotent)
34 - POST: Create resources (not idempotent)
35 - PUT: Replace entire resources (idempotent)
36 - PATCH: Partial updates (not idempotent)
37 - DELETE: Remove resources (idempotent)
38
393. **Use Appropriate Status Codes**
40 - 200 OK: Successful GET/PUT/PATCH
41 - 201 Created: Successful POST with Location header
42 - 204 No Content: Successful DELETE
43 - 400 Bad Request: Invalid request data
44 - 404 Not Found: Resource doesn't exist
45 - 409 Conflict: Duplicate resource
46 - 500 Internal Server Error: Unexpected errors
47
484. **Create Request/Response DTOs**
49 - Separate API contracts from domain entities
50 - Use Java records or Lombok `@Data`/`@Value`
51 - Apply Jakarta validation annotations
52 - Keep DTOs immutable when possible
53
545. **Implement Validation**
55 - Use `@Valid` annotation on `@RequestBody` parameters
56 - Apply validation constraints (`@NotBlank`, `@Email`, `@Size`, etc.)
57 - Handle validation errors with `MethodArgumentNotValidException`
58
596. **Set Up Error Handling**
60 - Use `@RestControllerAdvice` for global exception handling
61 - Return standardized error responses with status, error, message, and timestamp
62 - Use `ResponseStatusException` for specific HTTP status codes
63
647. **Configure Pagination**
65 - Use Pageable for large datasets
66 - Include page, size, sort parameters
67 - Return metadata with total elements, totalPages, etc.
68
698. **Add Security Headers**
70 - Configure CORS policies
71 - Set content security policy
72 - Include X-Frame-Options, X-Content-Type-Options
73
74**Validation checkpoints:**
75- After step 1-2: Verify URL structure follows REST conventions (/users not /getUsers)
76- After step 3: Test each endpoint returns correct status codes
77- After step 4-5: Validate DTOs with curl or HTTPie before proceeding
78- After step 6: Confirm error responses match standardized format
79
80## Examples
81
82### Basic CRUD Controller
83
84```java
85@RestController
86@RequestMapping("/v1/users")
87@RequiredArgsConstructor
88@Slf4j
89public class UserController {
90 private final UserService userService;
91
92 @GetMapping
93 public ResponseEntity<Page<UserResponse>> getAllUsers(
94 @RequestParam(defaultValue = "0") int page,
95 @RequestParam(defaultValue = "10") int pageSize) {
96 log.debug("Fetching users page {} size {}", page, pageSize);
97 Page<UserResponse> users = userService.getAll(page, pageSize);
98 return ResponseEntity.ok(users);
99 }
100
101 @GetMapping("/{id}")
102 public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
103 return ResponseEntity.ok(userService.getById(id));
104 }
105
106 @PostMapping
107 public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
108 UserResponse created = userService.create(request);
109 return ResponseEntity.status(HttpStatus.CREATED).body(created);
110 }
111
112 @PutMapping("/{id}")
113 public ResponseEntity<UserResponse> updateUser(
114 @PathVariable Long id,
115 @Valid @RequestBody UpdateUserRequest request) {
116 return ResponseEntity.ok(userService.update(id, request));
117 }
118
119 @DeleteMapping("/{id}")
120 public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
121 userService.delete(id);
122 return ResponseEntity.noContent().build();
123 }
124}
125```
126
127### Request/Response DTOs
128
129```java
130// Request DTO
131@Data
132@NoArgsConstructor
133@AllArgsConstructor
134public class CreateUserRequest {
135 @NotBlank(message = "User name cannot be blank")
136 private String name;
137
138 @Email(message = "Valid email required")
139 private String email;
140}
141
142// Response DTO
143@Data
144@NoArgsConstructor
145@AllArgsConstructor
146public class UserResponse {
147 private Long id;
148 private String name;
149 private String email;
150 private LocalDateTime createdAt;
151}
152```
153
154### Global Exception Handler
155
156```java
157@RestControllerAdvice
158@Slf4j
159public class GlobalExceptionHandler {
160
161 @ExceptionHandler(MethodArgumentNotValidException.class)
162 public ResponseEntity<ErrorResponse> handleValidationException(
163 MethodArgumentNotValidException ex, WebRequest request) {
164 String errors = ex.getBindingResult().getFieldErrors().stream()
165 .map(f -> f.getField() + ": " + f.getDefaultMessage())
166 .collect(Collectors.joining(", "));
167
168 ErrorResponse errorResponse = new ErrorResponse(
169 HttpStatus.BAD_REQUEST.value(),
170 "Validation Error",
171 "Validation failed: " + errors,
172 request.getDescription(false).replaceFirst("uri=", "")
173 );
174 return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
175 }
176
177 @ExceptionHandler(ResponseStatusException.class)
178 public ResponseEntity<ErrorResponse> handleResponseStatusException(
179 ResponseStatusException ex, WebRequest request) {
180 ErrorResponse error = new ErrorResponse(
181 ex.getStatusCode().value(),
182 ex.getStatusCode().toString(),
183 ex.getReason(),
184 request.getDescription(false).replaceFirst("uri=", "")
185 );
186 return new ResponseEntity<>(error, ex.getStatusCode());
187 }
188}
189```
190
191## Best Practices
192
193### 1. Use Constructor Injection
194```java
195@Service
196@RequiredArgsConstructor
197public class UserService {
198 private final UserRepository userRepository;
199}
200```
201
202### 2. Prefer Immutable DTOs (Java Records or `@Value`)
203```java
204public record UserResponse(Long id, String name, String email) {}
205```
206
207### 3. Implement Proper Transaction Management
208```java
209@Service
210@Transactional
211public class UserService {
212 @Transactional(readOnly = true)
213 public Optional<User> findById(Long id) { return userRepository.findById(id); }
214
215 @Transactional
216 public User create(User user) { return userRepository.save(user); }
217}
218```
219
220## Constraints and Warnings
221
2221. **Never expose entities directly** - Use DTOs to separate API contracts from domain models
2232. **Follow REST conventions** - Use nouns for resources (/users), correct HTTP methods, plural names, proper status codes
2243. **Handle all exceptions globally** - Use `@RestControllerAdvice`, never let raw exceptions bubble up
2254. **Always paginate large result sets** - Prevent performance issues and DDoS vulnerabilities
2265. **Validate all input data** - Use Jakarta validation annotations on request DTOs
2276. **Never expose sensitive data** - Don't log or expose passwords, tokens, PII
228
229## References
230
231- See `references/` directory for comprehensive reference material including HTTP status codes, Spring annotations, and detailed examples
232- Refer to the `developer-kit-java:spring-boot-code-review-expert` agent for code review guidelines
233- Review `spring-boot-dependency-injection/SKILL.md` for dependency injection patterns
234- Check `../spring-boot-test-patterns/SKILL.md` for testing REST APIs