Spring Boot Code Review Checklist
Rapid-scan checklist for Spring Boot code reviews. Use this to quickly identify issues, then dive into linked documentation for details.
Security Red Flags - Block PR Immediately
- SQL injection risk: String concatenation in SQL queries instead of parameterized queries → SQL Injection Prevention
- Sensitive data in logs: Credit cards, passwords, API keys, SSNs, PII in log statements → Sensitive Data Protection
- Hardcoded secrets: Passwords, API keys, tokens embedded in source code → Configuration Management
- Missing input validation: User input not validated with Bean Validation annotations → Input Validation
- Path traversal vulnerability: User input directly in file paths without validation/sanitization → Path Traversal Prevention
- Stack traces exposed: Error responses include stack traces or internal implementation details → Error Handling Security
- Entities exposed in API: JPA entities returned directly from REST controllers → REST Controllers
- Money as double/float: Financial amounts using floating-point types instead of BigDecimal → Entity Design
- Mass assignment vulnerability: Accepting entities directly in request body instead of DTOs → Mass Assignment Prevention
- XSS vulnerability: User input rendered without encoding or sanitization → XSS Prevention
Common Mistakes
Dependency Injection
- Field injection (
@Autowiredon fields) instead of constructor injection → Dependency Injection - Missing
finalmodifier on injected dependencies - Using
@Autowiredannotation when constructor injection works automatically - Circular dependencies between services (design smell)
Transaction Management
- Missing
@Transactionalannotation on service methods that modify data → Service-Layer Transactions @Transactionalon repository methods instead of service layer- Missing
readOnly = trueon read-only service methods → Service-Layer Transactions - Wrong transaction propagation settings for use case → Transaction Propagation
@Transactionalon private methods (doesn't work due to proxy limitations)- Transactions spanning multiple use cases (transaction boundaries too broad)
Configuration
- Using scattered
@Valueannotations instead of@ConfigurationProperties→ Configuration Management - Missing validation annotations on configuration properties
- Hardcoding environment-specific values instead of using profiles
- Sensitive configuration not externalized to environment variables
REST API Design
- Missing
@Validannotation on request bodies → Request Validation - Incorrect HTTP status codes (e.g., returning 200 for POST instead of 201) → HTTP Methods and Semantics
- Missing
@Validatedon controller class (needed for path variable validation) - No global exception handler for validation errors → Global Exception Handling
- Using
@Controllerinstead of@RestControllerfor REST APIs - Inconsistent error response format (not following RFC 7807) → Global Exception Handler
Data Access & JPA
- Using H2 for integration tests instead of TestContainers with real PostgreSQL → Why PostgreSQL Over H2
- Missing indexes on frequently queried columns or foreign keys
open-in-view: trueenabled (default) instead of disabled → Configuration- Entity attribute names don't match database column names (missing
@Column(name="...")) - Lazy-loaded collections accessed outside transaction boundary
- Missing
@NoArgsConstructor(access = AccessLevel.PROTECTED)on JPA entities
Watch For - Performance & Correctness Issues
N+1 Query Problems
- Lazy-loaded relationships accessed in loops → The N+1 Query Problem
- Missing
JOIN FETCHor@EntityGraphwhen loading entities with relationships → Solution 1: JOIN FETCH, Solution 2: @EntityGraph - EAGER fetch type on
@OneToManyor@ManyToManyrelationships - Fetching full entities when only few fields needed (missing projections) → Projections (DTO queries)
Pagination
- List endpoints without pagination support → Pagination
- Missing
@PageableDefaultannotation with sensible defaults - Unbounded
findAll()queries that could return thousands of records - Custom pagination implementation instead of using Spring Data
Pageable
Exception Handling
- Catching generic
Exceptioninstead of specific exception types - Swallowing exceptions without logging or rethrowing
- Exception messages exposing sensitive information or implementation details
- Using exceptions for control flow instead of validation
- Not handling
OptimisticLockingFailureExceptionfrom versioned entities → Optimistic Locking
Entity Design Issues
- Missing
@Versionfield for optimistic locking on entities with concurrent updates → Optimistic Locking - Using
EnumType.ORDINALinstead ofEnumType.STRING→ Entity Best Practices - Missing
@PrePersist/@PreUpdatecallbacks for timestamp management - Bidirectional relationships without proper management methods
- Missing
orphanRemoval = truewhen child entities should be deleted with parent
Logging Issues
- Using string concatenation in log statements instead of placeholders → Logging
- Wrong log levels (DEBUG for production errors, ERROR for informational messages)
- Not logging correlation IDs for request tracing
- Logging entire request/response objects (may contain sensitive data)
- Missing context in log messages (customer ID, transaction ID, etc.)
Modern Java & Spring Boot Features
- Not using Java records for immutable DTOs → Records for DTOs
- Not leveraging sealed classes for state modeling → Pattern Matching with Sealed Classes
- Using WebFlux/reactive when virtual threads would be simpler → Virtual Threads vs Reactive
- Not using
Optionalfor nullable return values → Optional for Nullable Values - Creating custom executors when
@Asyncwith virtual threads is available → Async Processing
Best Practices - Code Quality
General Spring Boot
- Constructor injection with
finalfields (use@RequiredArgsConstructor) → Constructor Injection - Feature-based package structure instead of layer-based → Project Structure
@ConfigurationPropertiesfor grouped, validated configuration → @ConfigurationProperties- Virtual threads enabled for Spring Boot 3.2+ applications → Virtual Threads
- MapStruct for entity-to-DTO mapping → DTO Mapping with MapStruct
REST APIs
- DTOs (records) for all request/response objects, never entities → REST Controllers
@Validon all request bodies with validation annotations → DTOs with Validation- Global exception handler with RFC 7807 Problem Details → Global Exception Handler
- OpenAPI annotations for documentation (
@Operation,@ApiResponse) → REST Controller Design - Pagination on all list endpoints → Pagination
- Proper HTTP status codes (201 for POST, 204 for DELETE, etc.) → HTTP Methods and Semantics
Data Access
@Transactional(readOnly = true)at service class level → Service-Layer Transactions- JOIN FETCH or
@EntityGraphfor loading related entities → N+1 Solutions - Projections for queries that don't need full entities → Projections
- BigDecimal for monetary amounts with proper precision/scale → Basic Entity
- Flyway migrations for all schema changes → Database Migrations with Flyway
- TestContainers with PostgreSQL for integration tests → Why PostgreSQL Over H2
Security
- Bean Validation on all DTOs accepting user input → Jakarta Bean Validation
- Parameterized queries for all database operations → Prevention: Parameterized Queries
- Sensitive data masked or omitted from logs → Never Log Sensitive Data
- Generic error messages to clients, detailed logs internally → Don't Expose Internal Details
- Encryption for sensitive data at rest → Encrypting Sensitive Data
Testing
- Unit tests with mocked dependencies for service layer logic → Spring Boot Testing
- Integration tests with TestContainers for repository layer → Spring Boot Testing
- Test coverage for happy path, error cases, and edge cases
- Mutation testing configured and passing threshold
- Contract tests for API endpoints consuming external services
Quick Scan Table
| Category | What to Check | Severity | Reference |
|---|---|---|---|
| Security | Parameterized queries (no string concat in SQL) | Block | SQL Injection |
| Security | No sensitive data in logs | Block | Sensitive Data |
| Security | No hardcoded secrets | Block | Configuration |
| Security | Input validation with Bean Validation | Block | Input Validation |
| Security | DTOs used (not entities in API) | Block | REST Controllers |
| Security | Generic error messages | Block | Error Handling |
| Transactions | @Transactional on service write methods | Fix | Transactions |
| Transactions | readOnly = true on read methods | Fix | Transactions |
| Performance | JOIN FETCH or @EntityGraph (avoid N+1) | Fix | N+1 Queries |
| Performance | Pagination on list endpoints | Fix | Pagination |
| API | @Valid on request bodies | Fix | Validation |
| GOOD: | API | Correct HTTP status codes | Review |
| GOOD: | DI | Constructor injection (not field) | Review |
| GOOD: | Config | @ConfigurationProperties (not @Value) | Review |
| Entities | @Version for optimistic locking | Fix | Optimistic Locking |
| Entities | EnumType.STRING (not ORDINAL) | Fix | Entities |
| Entities | BigDecimal for money (not double) | Block | Entity Design |
Legend:
- Block PR - Security vulnerability or data corruption risk
- Fix Before Merge - Performance issue, correctness problem, or likely bug
- [GOOD] Review & Discuss - Code quality improvement
Additional Considerations
Code Smells Beyond the Basics
- Services with too many dependencies (>5 injected fields indicates poor cohesion)
- Methods longer than 30-40 lines (consider extracting)
- Classes larger than 300-400 lines (consider splitting by feature)
- Duplicate validation logic across multiple services
- Business logic leaking into controllers or repositories
- Inconsistent naming conventions across the codebase
- Missing JavaDoc for public APIs and complex methods
- TODOs or FIXMEs without tracking tickets
Database-Specific Issues
- Missing database indexes on columns used in WHERE clauses
- Queries without LIMIT clause that could return unbounded results
- Using native SQL when JPQL would work (reduces portability)
- Cartesian joins (missing join conditions)
- Missing database constraints that duplicate application validation
- Migrations that can't be rolled back
- Schema changes without corresponding application code changes
Observability & Operations
- Missing correlation IDs in MDC for distributed tracing → Spring Boot Observability
- No metrics exposed for business-critical operations
- Missing health check indicators for external dependencies
- No structured logging (JSON format for log aggregation)
- Missing alerts configuration for critical errors
- No retry/circuit breaker for external service calls → Spring Boot Resilience
API Design Beyond Basics
- Missing API versioning strategy → API Versioning
- No rate limiting on public endpoints → Rate Limiting
- Missing CORS configuration for web clients → CORS Configuration
- Inconsistent naming conventions for endpoints
- Missing cache headers for cacheable responses
- No content negotiation for multiple formats
Block Checklist - Must Fix Before Merge
Use this final checklist before approving:
- No SQL injection vulnerabilities (all queries parameterized)
- No sensitive data in logs (passwords, cards, SSNs, API keys masked/omitted)
- No hardcoded secrets (credentials from environment)
- Input validation present on all user input
- No path traversal vulnerabilities
- Entities not exposed in REST APIs (DTOs used)
- No stack traces in API error responses
- BigDecimal used for all monetary amounts
- Write operations have
@Transactional - No unbounded queries (pagination on list endpoints)
Related Documentation
Core Spring Boot Guides:
- Spring Boot General Guidelines - DI, configuration, project structure
- Spring Boot Data Access - JPA, transactions, queries, migrations
- Spring Boot Security Practices - Input validation, OWASP protection
- Spring Boot API Design - REST APIs, validation, error handling
- Spring Boot Testing - Unit, integration, contract testing
- Spring Boot Observability - Logging, metrics, tracing
- Spring Boot Resilience - Circuit breakers, retries, timeouts
Cross-Cutting Topics: