Docs LogoDocs
Spring Boot NotesIntermediate

Building REST APIs

Writing REST controllers, HTTP status conventions, pagination, and CORS.

Building REST APIs

A Typical Controller

@RestController
@RequestMapping("/api/transactions")
public class TransactionController {

    private final TransactionService service;

    public TransactionController(TransactionService service) {
        this.service = service;
    }

    @GetMapping
    public List<TransactionResponse> getAll(
            @RequestParam(required = false) String category) {
        return service.findAll(category);
    }

    @GetMapping("/{id}")
    public TransactionResponse getById(@PathVariable Long id) {
        return service.findById(id);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public TransactionResponse create(@Valid @RequestBody TransactionRequest req) {
        return service.create(req);
    }

    @PutMapping("/{id}")
    public TransactionResponse update(@PathVariable Long id,
                                       @Valid @RequestBody TransactionRequest req) {
        return service.update(id, req);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) {
        service.delete(id);
    }
}

REST Conventions Worth Following

  • Use nouns for resources (/transactions), not verbs (/getTransactions).
  • Use HTTP methods to express intent: GET (read), POST (create), PUT (full update), PATCH (partial update), DELETE (remove).
  • Use plural resource names and nested paths for relationships: /users/{userId}/transactions.
  • Return the right status codes: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found, 409 Conflict.
  • Version your API when you need to break compatibility: /api/v1/....

Returning ResponseEntity for More Control

When you need custom headers or dynamic status codes, prefer ResponseEntity over relying only on @ResponseStatus:

@GetMapping("/{id}")
public ResponseEntity<TransactionResponse> getById(@PathVariable Long id) {
    return service.findByIdOptional(id)
            .map(ResponseEntity::ok)
            .orElseGet(() -> ResponseEntity.notFound().build());
}

Pagination & Sorting

@GetMapping
public Page<TransactionResponse> getAll(
        @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC)
        Pageable pageable) {
    return service.findAll(pageable);
}

Spring Data automatically parses ?page=0&size=20&sort=amount,desc query params into a Pageable.

CORS Configuration (global, cleaner than @CrossOrigin per-controller)

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://fintrack.vinayprabhakar.dev")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true);
    }
}
Last updated on July 15, 2026

On this page