| name | api-contract-review |
| description | REST API design validation, OpenAPI review, and contract-first development patterns |
API Contract Review
REST API Design Standards
Resource Naming
GET /api/v1/orders # List orders (with pagination)
GET /api/v1/orders/{id} # Get single order
POST /api/v1/orders # Create order
PUT /api/v1/orders/{id} # Full update
PATCH /api/v1/orders/{id} # Partial update
DELETE /api/v1/orders/{id} # Delete order
GET /api/v1/orders/{id}/items # Sub-resource listing
POST /api/v1/orders/{id}/cancel # Action on resource
HTTP Status Codes
@GetMapping("/{id}")
public ResponseEntity<OrderDto> getOrder(@PathVariable String id) {
return ResponseEntity.ok(orderService.findById(id));
}
@PostMapping
public ResponseEntity<OrderDto> createOrder(
@Valid @RequestBody CreateOrderRequest request) {
var order = orderService.create(request);
var location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}").buildAndExpand(order.id()).toUri();
return ResponseEntity.created(location).body(order);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrder(@PathVariable String id) {
orderService.delete(id);
return ResponseEntity.noContent().build();
}
Request and Response DTOs
public record CreateOrderRequest(
@NotBlank String customerId,
@NotEmpty @Valid List<OrderItemRequest> items,
@Size(max = 500) String notes
) {}
public record OrderItemRequest(
@NotBlank String productId,
@Min(1) @Max(1000) int quantity
) {}
public record OrderDto(
String id,
String customerId,
List<OrderItemDto> items,
OrderStatus status,
BigDecimal totalAmount,
String currency,
Instant createdAt,
Instant updatedAt
) {}
public record PagedResponse<T>(
List<T> content,
int page,
int size,
long totalElements,
int totalPages,
boolean hasNext
) {
public static <T> PagedResponse<T> from(Page<T> page) {
return new PagedResponse<>(
page.getContent(), page.getNumber(), page.getSize(),
page.getTotalElements(), page.getTotalPages(), page.hasNext());
}
}
Error Response Format
public record ErrorResponse(
String type,
String title,
int status,
String detail,
String instance,
Map<String, Object> extensions
) {}
OpenAPI Documentation
@RestController
@RequestMapping("/api/v1/orders")
@Tag(name = "Orders", description = "Order management operations")
public class OrderController {
@Operation(
summary = "Create a new order",
description = "Creates an order for the specified customer and items",
responses = {
@ApiResponse(responseCode = "201", description = "Order created",
content = @Content(schema = @Schema(implementation = OrderDto.class))),
@ApiResponse(responseCode = "400", description = "Invalid request body"),
@ApiResponse(responseCode = "422", description = "Business rule violation")
})
@PostMapping
public ResponseEntity<OrderDto> createOrder(
@Valid @RequestBody CreateOrderRequest request) {
}
}
Contract Review Checklist
- Naming -- Resources are nouns (plural), actions use HTTP methods
- Versioning -- API version in URL path (
/api/v1/...)
- Status Codes -- Correct HTTP status for each response scenario
- Validation -- Request DTOs have Bean Validation annotations
- Pagination -- List endpoints support pagination with consistent format
- Error Format -- RFC 7807 Problem Detail for all error responses
- HATEOAS -- Links included where navigation is needed
- Content Type -- Explicit
Accept and Content-Type handling
- Idempotency -- PUT and DELETE are idempotent, POST uses idempotency keys
- Security -- Authentication and authorization documented per endpoint
- Rate Limiting -- Rate limit headers in responses
- Deprecation -- Deprecated endpoints marked with
Sunset header