REST controller patterns, request handling, validation, exception handling, and WebFlux.
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping
public List<UserResponse> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return userService.findAll(PageRequest.of(page, size))
.map(UserResponse::from)
.getContent();
}
@GetMapping("/{id}")
public UserResponse findById(@PathVariable Long id) {
return userService.findById(id)
.map(UserResponse::from)
.orElseThrow(() -> new ResourceNotFoundException("User", id));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResponse create(@Valid @RequestBody CreateUserRequest request) {
User user = userService.create(request);
return UserResponse.from(user);
}
@PutMapping("/{id}")
public UserResponse update(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
User user = userService.update(id, request);
return UserResponse.from(user);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
userService.delete(id);
}
}
public record CreateUserRequest(
@NotBlank @Size(min = 2, max = 100)
String name,
@NotBlank @Email
String email,
@NotBlank @Size(min = 8)
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).*$",
message = "must contain letters and numbers")
String password
) {}
public record UpdateUserRequest(
@Size(min = 2, max = 100)
String name,
@Email
String email
) {}
public record UserResponse(
Long id,
String name,
String email,
LocalDateTime createdAt
) {
public static UserResponse from(User user) {
return new UserResponse(
user.getId(),
user.getName(),
user.getEmail(),
user.getCreatedAt()
);
}
}
// Paginated response
public record PageResponse<T>(
List<T> content,
int page,
int size,
long totalElements,
int totalPages,
boolean last
) {
public static <T> PageResponse<T> from(Page<T> page) {
return new PageResponse<>(
page.getContent(),
page.getNumber(),
page.getSize(),
page.getTotalElements(),
page.getTotalPages(),
page.isLast()
);
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse("NOT_FOUND", ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidation(MethodArgumentNotValidException ex) {
Map<String, String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage,
(a, b) -> a
));
return new ErrorResponse("VALIDATION_ERROR", "Validation failed", errors);
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleGeneric(Exception ex) {
log.error("Unexpected error", ex);
return new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred");
}
}
public record ErrorResponse(
String code,
String message,
Map<String, String> details
) {
public ErrorResponse(String code, String message) {
this(code, message, null);
}
}
// Path variables
@GetMapping("/{userId}/orders/{orderId}")
public Order getOrder(
@PathVariable Long userId,
@PathVariable Long orderId) {}
// Query parameters
@GetMapping("/search")
public List<User> search(
@RequestParam String query,
@RequestParam(required = false) String status,
@RequestParam(defaultValue = "name") String sortBy) {}
// Headers
@PostMapping
public void create(
@RequestHeader("X-Request-Id") String requestId,
@RequestHeader(value = "X-Tenant-Id", required = false) String tenantId) {}
// Request body
@PostMapping
public void create(@RequestBody @Valid CreateRequest request) {}
// Multipart file
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void upload(@RequestPart("file") MultipartFile file) {}
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer