Spring REST APICheat Sheet
Quick Reference · Spring Boot · REST · OpenAPI · Validation · Pagination
RESTSpring MVCSwaggerValidationPaginationWebClient
REST Six Principles
Uniform Interface
Unique resource IDs · uniform representations · self-descriptive msgs · client drives via hyperlinks (HATEOAS)
Client–Server
UI separated from data storage/server
Stateless
Every request contains ALL info to complete it — no server session
Cacheable
Response must label itself cacheable or non-cacheable
Layered System
Hierarchical layers; component behavior constrained (e.g. MCP)
Code on Demand
Optional — server can send executable code (applets/scripts)
HATEOAS
Client only needs URI. API responses include hypermedia links for self-discovery of next actions & resources.
RMM Richardson Maturity Model
L0Swamp of POX
One URI, one HTTP method. Body tells server what to do. Essentially RPC (SOAP/XML). Not REST.
L1Resources
Multiple URIs per resource, but still one HTTP method (usually POST) for all actions.
L2HTTP Verbs
Correct HTTP verbs (GET/POST/PUT/DELETE) + proper status codes (201, 404, 400).
L3Hypermedia Controls
HATEOAS — responses include _links for discoverability & self-documentation.
// L3 HATEOAS response example { "id": 123, "status": "SHIPPED", "_links": { "self": { "href": "/orders/123" }, "track": { "href": "/orders/123/tracking" }, "cancel": { "href": "/orders/123/cancel" } } }
HTTP Methods
MethodUseIdempotentBody
GETRead resource
POSTCreate resource
PUTReplace resource
PATCHPartial update~
DELETERemove resource
HEADGET without body (existence check)
OPTIONSList supported methods
⚠ Anti-pattern: Never GET /users/delete?id=10 — GET has no side effects. Browsers/crawlers auto-call GETs.
Status Codes
200 OK201 Created204 No Content400 Bad Request401 Unauthorized403 Forbidden404 Not Found409 Conflict500 Internal
ERRORS Exception Handling
// 1. Custom Exception public class EmployeeNotFoundException extends RuntimeException { public EmployeeNotFoundException(Long id) { super("Employee not found: " + id); } } // 2. ApiError response DTO public class ApiError { LocalDateTime timestamp; int status; String message; Map<String, String> errors; } // 3. Global Handler @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(EmployeeNotFoundException.class) public ResponseEntity<ApiError> handleNotFound( EmployeeNotFoundException ex) { ApiError err = new ApiError( LocalDateTime.now(), 404, ex.getMessage(), null); return ResponseEntity.status(404).body(err); } @ExceptionHandler( MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ApiError handleValidation( MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach( e -> errors.put(e.getField(), e.getDefaultMessage())); return new ApiError( LocalDateTime.now(), 400, "Validation failed", errors); } }
VALID Bean Validation
Dependency: spring-boot-starter-validation
// Model — Jakarta annotations public class Employee { @NotBlank(message = "Name required") private String name; @Email(message = "Invalid email") @NotBlank private String email; @Min(0) @Max(500000) private Double salary; @NotNull @Size(min=2, max=50) private String department; @Pattern(regexp = "^[A-Z]{2}\d{4}$") private String code; } // Controller — trigger with @Valid @PostMapping @ResponseStatus(HttpStatus.CREATED) public Employee create( @Valid @RequestBody Employee employee) { return service.create(employee); } // Throws MethodArgumentNotValidException on fail
All Annotations
@NotNull@NotEmpty@NotBlank@Size@Min@Max@Email@Pattern@Positive@Negative@Future@DecimalMin@DecimalMax@AssertTrue@AssertFalse@Null
@Valid triggers on: class property · method param · method return type
PAGE Pagination & Sorting
// Controller @GetMapping public Page<Employee> getAll(Pageable pageable) { return service.getAll(pageable); } // Auto-maps: ?page=0&size=20&sort=name,asc // Repository public interface EmployeeRepo extends PagingAndSortingRepository<Employee, Long> {} // Exposes: /employees{?page,size,sort} // Manual pagination (non-repo data, e.g. SOAP) int start = (int) pageable.getOffset(); int end = Math.min(start + pageable.getPageSize(), list.size()); return new PageImpl<>( list.subList(start, end), pageable, list.size()); // Page object contents page.getContent() // the list page.getTotalElements() // total records page.getTotalPages() // total pages page.getNumber() // current page page.isFirst() / isLast() page.hasNext() / hasPrevious()
HATEOAS Discoverability
Use events to decouple link-building from controller. Publish event → listener adds Link headers. Keeps controller clean.
Test: first page has next no prev · middle has both · last has no next
DOCS OpenAPI / Swagger (springdoc)
Dependencies
springdoc-openapi-starter-webmvc-ui
(webflux → -webflux variant)
Default URLs
/v3/api-docs JSON spec
/swagger-ui/index.html
# application.properties springdoc.api-docs.path=/api-docs springdoc.swagger-ui.path=/swagger-ui-custom.html springdoc.swagger-ui.tagsSorter=alpha springdoc.writer-with-order-by-keys=true server.forward-headers-strategy=framework # behind proxy
// Config Bean @Bean public OpenAPI customOpenAPI() { return new OpenAPI().info(new Info() .title("Employee API").version("1.0") .description("...")); } // Controller annotations @Tag(name = "Employee API", description = "...") @Operation(summary = "Create a new employee") @ApiResponses({ @ApiResponse(responseCode = "201", description = "Created"), @ApiResponse(responseCode = "400", description = "Validation error", content = @Content(mediaType="application/json", schema = @Schema(impl = ApiError.class))) }) @Parameter(description = "employee id") @PathVariable // Global tag ordering (controls Swagger display order) @OpenAPIDefinition(tags = { @Tag(name = "create"), @Tag(name = "find"), @Tag(name = "update"), @Tag(name = "delete") }) // Bean validation → auto schema constraints // @ResponseStatus on advice → auto response codes // Pageable → auto page/size/sort params (v1.6.0+)
Swagger fails? Check:
① GET /v3/api-docs returns JSON ② dependency present ③ path not customized ④ security not blocking ④ CSRF allowed for docs path ⑤ context path prefix set correctly
DESIGN API Design Best Practices
URI Design
/usersvs/getUser
/users/12/ordersvs/getUserOrders?id=12
kebab-casevscamelCase in URI
/users (plural)vs/user & /users mixed
Best Practices
URIs = nouns, not verbs
Use correct HTTP verb always
Richardson Maturity Model
Centralize errors (@ControllerAdvice)
Version from day 1 (/api/v1/users)
Stateless — JWT/OAuth2 for auth
Filtering + pagination on list endpoints
No file extensions in URIs
Accept header for content negotiation
No state stored on server
Versioning Strategies
// URI-based (separate classes) @RequestMapping("/api/v1/users") class UserV1Controller @RequestMapping("/api/v2/users") class UserV2Controller // Header-based (same controller) @GetMapping(headers = "X-API-VERSION=1") public Employee getV1() { ... } @GetMapping(headers = "X-API-VERSION=2") public EmployeeV2 getV2() { ... }
CONTENT Content Negotiation
// pom.xml — add XML support <dependency>jackson-dataformat-xml</dependency> // DTO — mark XML root element @JacksonXmlRootElement(localName = "employees") public class EmployeeResponse { ... } // Controller — support both @RequestMapping(value="/employees", produces = {"application/json","application/xml"}) // produces → filters by client Accept header // consumes → filters by client Content-Type header // Spring picks best match automatically // Both on a method: @PostMapping( consumes = "application/json", produces = {"application/json","application/xml"})
INTERNALS Spring REST Request Flow
Client Request
TCP :8080
Acceptor Thread
Queues request
Worker Thread
Handles from queue
DispatcherServlet
extends HttpServlet · doService()
HandlerMapping
getHandler() loop
Controller Method
@RestController
// DispatcherServlet initializes handler mappings on startup // initHandlerMappings() called during instantiation // HandlerMapping interface → maps requests to handlers // Acceptor: queues incoming connections (rejects when full) // Worker: executes requests in dedicated thread stacks // Core Spring REST annotations @RestController // @Controller + @ResponseBody @RequestMapping("/api/v1/employees") @GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping @PathVariable @RequestParam @RequestBody @ResponseStatus(HttpStatus.CREATED)
WEBCLIENT Consuming REST APIs
// 1. Dependency: spring-boot-starter-webflux // 2. Config bean @Configuration public class WebClientConfig { @Bean public WebClient webClient() { return WebClient.builder().build(); } } // 3. DTO public class WeatherResponse { private Double temperature; private Double windspeed; // constructor, getters, setters } // 4. Service @Service public class WeatherService { private final WebClient webClient; public WeatherResponse getWeather(String city) { Map response = webClient.get() .uri("https://api.weather.com/v1?city=" + city) .retrieve() .bodyToMono(Map.class) .block(); Map curr = (Map) response.get("current_weather"); return new WeatherResponse( curr.get("temperature"), curr.get("windspeed")); } }
SCHEMA JSON Schema & DTO
JSON Schema
Define types: string, object, array
Constraints: regex, required fields
Use $ref + definitions for reuse
Nesting for complex structures
JSON Hyper-schema: adds HTTP endpoints (href), methods & relationships (rel)
schema: validates request body
targetSchema: defines response shape
Meta-schemas: schema that validates your schema
DTO Pattern
Never expose entities directly — schema changes break clients.
DTO = Data Transfer Object = contract between API and client.
Schema-First Tools
prmddocumentation
heroicsclient libraries
committeevalidation & testing
MAVEN OpenAPI Generation Plugin (pom.xml)
<plugin> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-maven-plugin</artifactId> <version>1.4</version> <executions> <execution> <phase>integration-test</phase> <goals><goal>generate</goal></goals> </execution> </executions> <configuration> <apiDocsUrl> http://localhost:8080/v3/api-docs </apiDocsUrl> <outputFileName>openapi.json</outputFileName> <outputDir>${project.build.directory}</outputDir> </configuration> </plugin>
Key Dependency Summary
spring-boot-starter-webREST controllers, MVC, Tomcat
spring-boot-starter-validationJakarta Bean Validation
spring-boot-starter-webfluxWebClient (consume APIs), reactive
springdoc-openapi-starter-webmvc-uiSwagger UI + OpenAPI docs
jackson-dataformat-xmlXML content negotiation
spring-boot-starter-data-jpaPagingAndSortingRepository
REST Notes
REST ≠ HTTP
REST defines HOW resource methods should behave. HTTP provides a protocol. An API can be RESTful without HTTP.
PUT vs PATCH
PUT = idempotent full replacement. PATCH = partial update, conditionally idempotent (multiple calls may differ).
Pagination as Representation
Page is NOT a resource. Treat it as a representation — use query params (?page=0&size=20&sort=name,asc).
Cross-cutting Concerns
Pagination, HATEOAS links, and error handling are cross-cutting — use events & advice to keep controllers clean.
SPRING REST CHEAT SHEET · QUICK REFERENCE