RU | EN | DE

1. Basic REST Controller

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService service;
    @GetMapping
    public List<UserDto> getAll() {
        return service.allUsers();
    }
 
    @GetMapping("/{id}")
    public UserDto getById(@PathVariable Long id) {
        return service.getUser(id);
    }
 
    @PostMapping
    public UserDto create(@Valid @RequestBody UserDto dto) {
        return service.create(dto);
    }
 
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        service.delete(id);
    }
}

📌 Annotations:

  • @RestController = @Controller + @ResponseBody
  • @RequestMapping → base prefix (/api/users)
  • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping

2. Validation (JSR-380 Bean Validation)

📄 DTO:

public record UserDto(
    @NotBlank String name,
    @Email String email,
    @Min(18) int age
) {}

📄 In the controller:

@PostMapping
public UserDto create(@Valid @RequestBody UserDto dto) {
    return service.create(dto);
}

📄 Error Handling:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors()
            .forEach(e -> errors.put(e.getField(), e.getDefaultMessage()));
        return ResponseEntity.badRequest().body(errors);
    }
}

3. Error Handling (ProblemDetail — Spring 6, RFC 7807)

@RestControllerAdvice
class ApiExceptionHandler {
    @ExceptionHandler(IllegalArgumentException.class)
    ProblemDetail handleIllegalArgument(IllegalArgumentException e) {
        ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
        pd.setTitle("Validation failed");
        pd.setDetail(e.getMessage());
        return pd;
    }
}

📌 The JSON response will be in the format:

{
  "type": "about:blank",
  "title": "Validation failed",
  "status": 400,
  "detail": "Invalid email"
}

4. Pagination and Sorting

Spring Data JPA handles this automatically:

@GetMapping
public Page<UserDto> all(@RequestParam int page, @RequestParam int size) {
    return repo.findAll(PageRequest.of(page, size, Sort.by("name").ascending()));
}

📌 The JSON response will have the fields:

{
  "content": [...],
  "totalPages": 5,
  "totalElements": 100,
  "number": 0,
  "size": 20
}

5. Filtering (Specification API)

public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {}
 
@GetMapping("/search")
public List<User> search(@RequestParam(required=false) String name,
                        @RequestParam(required=false) Integer minAge) {
    return repo.findAll((root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (name != null) predicates.add(cb.like(root.get("name"), "%" + name + "%"));
        if (minAge != null) predicates.add(cb.greaterThanOrEqualTo(root.get("age"), minAge));
        return cb.and(predicates.toArray(new Predicate[0]));
    });
}

6. Uploading, Downloading Files

Upload

@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) throws IOException {
    Path path = Paths.get("uploads/" + file.getOriginalFilename());
    Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
    return "Uploaded: " + path;
}

Download

@GetMapping("/download/{filename}")
public ResponseEntity<Resource> download(@PathVariable String filename) throws IOException {
    Path path = Paths.get("uploads/" + filename);
    Resource resource = new UrlResource(path.toUri());
    return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
            .body(resource);
}

7. Rate Limiting (Request Limiting)

📄 Simple option (Resilience4j + Spring Boot Starter):

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>

📄 Config:

resilience4j.ratelimiter:
    instances:
        apiLimiter:
            limitForPeriod: 5
            limitRefreshPeriod: 10s
            timeoutDuration: 0

📄 Usage:

@RestController
@RequiredArgsConstructor
public class DemoController {
    @RateLimiter(name = "apiLimiter")
    @GetMapping("/limited")
    public String limited() {
        return "OK";
    }
}

8. Documentation (Swagger, OpenAPI)

📄 Adding dependency:

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.5.0</version>
</dependency>

📌 Now available:

9. CORS (Cross-Origin Resource Sharing)

@Configuration
public class CorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                        .allowedOrigins("http://localhost:3000")
                        .allowedMethods("GET","POST","PUT","DELETE");
            }
        };
    }
}

10. Best Practices

  • ✅ Use DTO + @Valid instead of Entity directly.
  • ✅ Handle errors through @RestControllerAdvice.
  • ✅ Always add pagination/sorting to GET requests.
  • ✅ For APIs, it is better to use ProblemDetail (RFC 7807).
  • ✅ Document the API through Swagger/OpenAPI.
  • ✅ Add rate limiting for protection.