Java + Spring Boot Development
When to use
- Scaffolding or extending a Spring Boot 3.x service (REST, gRPC, batch, messaging)
- Configuring Spring DI, application context, or Spring component scanning
- Mapping domain objects to the database via Spring Data JPA / Hibernate
- Implementing authentication and authorisation with Spring Security 6
- Adding observability (Actuator, Micrometer, distributed tracing with Micrometer Tracing)
- Writing tests: unit, slice (
@DataJpaTest,@WebMvcTest), and integration with Testcontainers - Adopting Java 21+ features: virtual threads (Project Loom), records, sealed classes, pattern matching
Workflow
- Scaffold with start.spring.io or
spring initializrCLI — choose Java 21+, Maven or Gradle (Kotlin DSL preferred), and the minimal starter set needed. - Structure the layers:
Keepsrc/main/java/com/example/ api/ ← Controllers / DTOs (presentation) service/ ← Business logic (application) domain/ ← Entities, value objects, repository interfaces infra/ ← JPA repositories, external HTTP clients, config@Service,@Repository, and@Controller/@RestControllerin separate packages — scanning is implicit but the layer contract must be explicit. - Define DTOs / request-response contracts first using Java records; add
@ValidBean Validation annotations (@NotBlank,@Size,@Email,@Pattern) and a global@ControllerAdvice/@ExceptionHandlerto return structured RFC 7807 Problem responses. - Map the data model — see
.claude/skills/data-modeling/SKILL.md. Annotate@Entityclasses conservatively: useFetchType.LAZYfor all associations by default; use@BatchSizeorJOIN FETCHin the repository to avoid N+1. - Secure the application:
- Spring Security 6: define a
SecurityFilterChainbean; preferrequestMatchersoverantMatchers. - Stateless REST: JWT via
spring-security-oauth2-resource-server+BearerTokenAuthenticationFilter. Never roll custom JWT parsing. - Enable CSRF only for browser-session flows; disable for pure API services.
- Use method security (
@PreAuthorize("hasRole('ADMIN')")) for fine-grained control.
- Spring Security 6: define a
- Enable Actuator: expose
health,info,metrics,prometheusendpoints on a separate management port (8081). Hide sensitive endpoints behindmanagement.endpoint.*.enabled. - Adopt virtual threads (Spring Boot 3.2+): set
spring.threads.virtual.enabled=true— eliminates thread-pool tuning for I/O-heavy services. Do not use virtual threads for CPU-bound compute; use dedicated@Asyncexecutors with a bounded pool there. - Write tests in layers:
- Unit: plain JUnit 5 + Mockito for services/domain logic.
- Slice:
@WebMvcTestfor controllers;@DataJpaTestwith an in-memory H2 or Testcontainers Postgres for repositories. - Integration:
@SpringBootTest(webEnvironment = RANDOM_PORT)+ Testcontainers for the full stack.
- Profile slow queries: enable
spring.jpa.show-sql=falsein production; use Hibernate statistics (hibernate.generate_statistics=true) in dev; log queries >1 s with P6Spy or datasource-proxy. - Audit before deploy against
.claude/checklists/security.mdand.claude/checklists/production.md.
Standards
Dependency Injection
- All injectable components are Spring beans — never call
newon a service class. - Constructor injection only;
@Autowiredon fields is banned (makes testing hard and hides dependencies). - Use
@ConfigurationProperties(prefix = "app.feature")+ a record/POJO for typed, validated configuration — never@Value("${…}")scattered through business code. @Beanfactory methods go in@Configurationclasses named<Feature>Configuration; do not put@Beanin@Serviceclasses.
JPA / Hibernate
FetchType.LAZYfor all@ManyToOne,@OneToMany,@ManyToMany. Eager loading is the source of most JPA performance bugs.- Use
@EntityGraphorJOIN FETCHin the repository method that needs the data — co-locate the fetch strategy with the query. - Prefer Spring Data derived queries or
@QueryJPQL for simple reads; useJdbcTemplateorjOOQfor complex reporting queries — do not abuse JPQL for 10-join analytics. - Never expose JPA entities as REST response bodies — always map to DTOs. This prevents lazy-initialisation serialisation failures and over-exposure.
@Transactional(readOnly = true)on query-only service methods — this sets the Hibernate flush mode to NEVER and allows read-only JDBC optimisations.- Migrations: use Flyway (preferred) or Liquibase, never
ddl-auto=updatein any environment beyond local dev.
Validation
- Validate at the API boundary with
@Validon@RequestBodyand method parameters. - Custom constraints: implement
ConstraintValidator<A, T>— do not hand-roll if-else chains in service code. - Return 400 with a structured error body listing all violations; never 500 for validation failures.
Security
- Password storage:
BCryptPasswordEncoder(cost ≥ 12) orArgon2PasswordEncoder— never MD5, SHA-1, or plain text. - Secrets from environment variables or a secret manager (AWS Secrets Manager, Vault) — never in
application.ymlcommitted to VCS. - HTTP Security headers: add
X-Content-Type-Options,X-Frame-Options,Content-Security-Policyviaheaders()inSecurityFilterChain.
Do not
- Do not use
spring.jpa.hibernate.ddl-auto=updatein staging or production. - Do not catch
Exceptiongenerically and swallow it; catch specific exceptions and convert to typed domain errors. - Do not use
@SpringBootTestfor everything — slice tests are 10x faster. - Do not expose
env,beans, orhttptraceActuator endpoints publicly. - Do not block virtual threads with
synchronizedon contested monitors — useReentrantLockinstead (virtual-thread pinning).
Common mistakes to avoid
| Mistake | Fix |
|---|---|
N+1 queries from FetchType.LAZY in a loop |
Use @EntityGraph or JOIN FETCH in the repository for that specific use case |
LazyInitializationException outside a transaction |
Move the fetch inside the @Transactional boundary or use a DTO projection |
@Transactional on private methods |
Spring AOP proxies only intercept public methods; move logic to a public method or use AspectJ weaving |
| Fat controllers with business logic | Business logic lives in @Service; controllers only parse, delegate, and map responses |
application.properties with plain-text passwords |
Use spring.config.import=optional:configserver: or environment variable references |
| Circular bean dependencies | Introduce an interface, event, or factory to break the cycle; do NOT use @Lazy as a workaround |
Missing @Transactional on multi-step mutations |
Any operation that writes to more than one table must be wrapped in a transaction |
Output format
- New service: directory layout with
pom.xml/build.gradle.ktsexcerpt,application.ymlskeleton, andSecurityFilterChainbean. - Entity + repository: annotated
@Entityclass, Spring DataJpaRepositoryinterface, and a Flyway migration SQL file. - Test: JUnit 5 class using
@WebMvcTestor@DataJpaTestwith MockMvc assertions and Testcontainers setup. - Security config: complete
SecurityFilterChain@Beanwith JWT resource-server configuration.
Output artifacts go to docs/specs/ (architecture decisions) or alongside source files (test classes, migration SQL).
Related checklists
- .claude/checklists/security.md
- .claude/checklists/performance.md
- .claude/checklists/qa.md
- .claude/checklists/production.md
Related agents
- .claude/agents/core/solution-architect.md
- .claude/agents/engineering/backend-engineer.md
- .claude/agents/engineering/database-architect.md
- .claude/agents/quality/security-auditor.md