Controller Layer
📝 Note: This guide uses generic placeholder names to be reusable across any Spring Boot microservice. Replace with your actual implementation:
{YourService}→ Your service name (e.g.,OrderService,PaymentService)BusinessService→ Your core service (e.g.,OrderService,UserService)DataService→ Your data processing service (e.g.,PaymentService,InventoryService)IntegrationService→ Your external integration (e.g.,PaymentGatewayService){RequestType}→ Your request DTO (e.g.,CreateOrderRequest){ResponseType}→ Your response DTO (e.g.,OrderResponse)
Overview
The controller layer handles HTTP requests, validates inputs, delegates to services, and maps results to HTTP responses. Controllers act as the entry point for all REST API operations, following a thin controller pattern with minimal logic.
Key Principles
- Contract-First Development: Implement OpenAPI-generated interfaces from
skeleton/package - Thin Controllers: Only validation and delegation - no business logic
- Constructor Injection: Use constructor injection for all dependencies (no field injection)
- Fail Fast: Validate all inputs before delegating to services
- Let Exceptions Bubble: Don't catch exceptions - let
@ControllerAdvicehandle them
Architecture Pattern
controller/
├── skeleton/ # OpenAPI-generated interfaces (auto-generated)
│ ├── BusinessApi.java
│ ├── TokenApi.java
│ └── CutoverConfigApi.java
└── impl/ # Controller implementations
├── BusinessApiImpl.java
├── DataApiImpl.java
└── CutoverConfigApiImpl.java
Request Flow
HTTP Request
↓
Controller validates input
↓
Controller delegates to service
↓
Service returns result or throws exception
↓
Controller returns ResponseEntity
↓
Exception? → @ControllerAdvice handles it
Detailed Guides
Design Patterns
📄 Design Patterns - REST controller, interface-implementation, thin controller, facade, adapter, MVC, builder, header-based routing, and OpenAPI contract-first patterns
Best Practices
📄 Best Practices - Constructor injection, input validation, keeping controllers thin, using ResponseEntity, exception handling, Javadoc references, header guidelines, wrapper pattern, code formatting, performance, and security
Testing
📄 Testing Guide - Unit testing with Mockito, integration testing with MockMvc, testing validation and service exceptions, test data builders, naming conventions, and coverage goals
Anti-Patterns
📄 Anti-Patterns - What to avoid: business logic in controllers, field injection, catching all exceptions, direct database access, complex parameter lists, response transformation, duplicate validation, stateful controllers, ignoring HTTP status codes, and blocking long operations
Code Examples
Core Patterns
- 📝 Constructor Injection - Recommended dependency injection pattern with immutable dependencies
- 📝 Thin Controller - Validate → delegate → respond pattern with no business logic
- 📝 Header-Based Routing - Multi-tenancy, platform routing, and client metadata via headers
- 📝 OpenAPI Contract-First - Implementing generated interfaces for type-safe APIs
Quick Reference
Controller Responsibilities Checklist
✅ Controllers SHOULD:
- Implement OpenAPI-generated interfaces
- Validate inputs using validator classes
- Delegate to service layer
- Return ResponseEntity with appropriate status
- Handle HTTP-specific concerns (headers, status codes)
- Be thin and simple
❌ Controllers SHOULD NOT:
- Contain business logic
- Make database calls directly
- Perform complex calculations
- Catch and handle domain exceptions
- Have cyclic dependencies
- Store state (should be stateless)
Standard HTTP Status Codes
| Operation | Status Code | Usage |
|---|---|---|
| Success | 200 OK | Standard successful response |
| Created | 201 Created | Resource creation successful |
| No Content | 204 No Content | Successful delete/update with no body |
| Accepted | 202 Accepted | Async operation accepted |
| Bad Request | 400 | Invalid input (validation failure) |
| Unauthorized | 401 | Authentication required |
| Forbidden | 403 | Authenticated but not authorized |
| Not Found | 404 | Resource doesn't exist |
| Internal Error | 500 | Unexpected server error |
Common Headers
| Header | Required | Purpose | Example |
|---|---|---|---|
X-AMCN-TENANT |
✅ | Multi-tenant routing | amcplus, sundancenow |
X-AMCN-SERVICE-ID |
✅ | Service identifier | amc, sundance |
X-AMCN-PLATFORM |
✅ | Client platform | ios, android, web |
X-AMCN-NETWORK |
✅ | Network identifier | AMC, SUNDANCE |
X-Country-Code |
✅ | Country code | US, CA |
X-AMCN-LANGUAGE |
✅ | User language | en, es |
X-AMCN-DEVICE-ID |
⚠️ | Device ID (conditional) | UUID |
X-AMCN-ACCESS-TOKEN |
⚠️ | Auth token (conditional) | JWT |
X-AMCN-APP-VERSION |
❌ | Client version | 1.2.3 |
X-AMCN-TEST-CONTEXT |
❌ | Testing flags | flag1,flag2 |
Code Formatting
# Check formatting
./gradlew spotlessCheck
# Apply formatting
./gradlew spotlessApply
All code must follow Google Java Format (AOSP style) with Spotless.
Related Layers
- Service Layer: Business logic and orchestration (controllers delegate to services)
- Validator Layer: Input validation logic (injected into controllers)
- Model Layer: Request/response DTOs (OpenAPI-generated schemas)
- Exception Layer: Exception handlers with
@ControllerAdvice
Summary
Controllers in this layer follow a clean, REST-based architecture using:
- ✅ Contract-first development with OpenAPI
- ✅ Thin controller pattern (validation + delegation)
- ✅ Constructor injection for dependencies
- ✅ Header-based routing and multi-tenancy
- ✅ Centralized exception handling
- ✅ Automated code formatting with Spotless
Keep controllers simple, stateless, and focused solely on HTTP concerns.