Example CLAUDE.md — Java/Spring Boot Project¶
This example shows a CLAUDE.md for a Java backend service using Spring Boot 3, Spring Data JPA, and Gradle. It covers Java-specific conventions for package structure, dependency injection, and testing patterns that keep Claude Code generating production-quality Java.
The CLAUDE.md File¶
````markdown
Project: Acme Customer API¶
Java 21 + Spring Boot 3.3 REST API. Gradle build system. PostgreSQL database with Spring Data JPA.
Commands¶
./gradlew bootRun— start the server (port 8080)./gradlew test— run all tests./gradlew test --tests "com.acme.customer.service.CustomerServiceTest"— run a single test class./gradlew test --tests "*.CustomerServiceTest.shouldCreateCustomer"— run a single test method./gradlew spotlessCheck— check code formatting./gradlew spotlessApply— auto-format code./gradlew check— run all checks (tests + spotless + checkstyle)docker compose up -d— start PostgreSQL and Redis locally
Run ./gradlew check before committing.
Architecture¶
Standard layered Spring Boot architecture:
src/main/java/com/acme/customer/controller/— REST controllers, one per resourceservice/— business logic, interfaces + implementationsrepository/— Spring Data JPA repositoriesmodel/— JPA entity classesdto/— request/response DTOs (records)mapper/— MapStruct mappers for entity-DTO conversionconfig/— Spring configuration classesexception/— custom exceptions and global exception handlersecurity/— Spring Security configuration and filterssrc/main/resources/application.yml— main configapplication-local.yml— local dev overridesdb/migration/— Flyway migration scriptssrc/test/java/— mirrors main structure
Coding Conventions¶
- Java 21 features: use records for DTOs, pattern matching, sealed interfaces where appropriate
- Constructor injection only — no field injection with
@Autowired - Use
finalon all fields, parameters, and local variables where possible - DTOs are Java records, not classes:
java
public record CreateCustomerRequest(
@NotBlank String name,
@Email String email,
@NotNull CustomerType type
) {}
- Service interfaces with single implementation:
CustomerServiceinterface +CustomerServiceImpl - All public methods in services have Javadoc
- Use
Optional<T>return types for single-entity lookups — never return null - Use
lombokonly for@Slf4j— do not use@Data,@Getter,@Setteron new code (use records)
Database¶
- Flyway for migrations — files in
src/main/resources/db/migration/ - Naming:
V001__create_customers_table.sql,V002__add_email_index.sql - JPA entities use
@Entitywith explicit@Table(name = "...")and@Column(name = "...") - Always specify column lengths and constraints in the entity
- Use
@Versionfor optimistic locking on frequently updated entities - Repositories extend
JpaRepository<T, ID>— add custom query methods with@Queryor derived queries
```java @Entity @Table(name = "customers") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
@Column(name = "name", nullable = false, length = 255)
private String name;
@Column(name = "email", nullable = false, unique = true)
private String email;
@Version
private Long version;
} ```
REST Controllers¶
```java @RestController @RequestMapping("/api/v1/customers") @RequiredArgsConstructor public class CustomerController { private final CustomerService customerService;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public CustomerResponse create(@Valid @RequestBody CreateCustomerRequest request) {
return customerService.create(request);
}
@GetMapping("/{id}")
public CustomerResponse getById(@PathVariable Long id) {
return customerService.getById(id);
}
} ```
- Use
@Validon all request body parameters - Return DTOs, never entities
- Use
@ResponseStatusfor non-200 success codes - Paginated endpoints return
Page<T>with Spring'sPageableparameter
Exception Handling¶
Global exception handler in exception/GlobalExceptionHandler.java:
@RestControllerAdvicewith@ExceptionHandlermethods- Custom exceptions:
ResourceNotFoundException,BusinessValidationException,ConflictException - Return structured error responses:
{ "error": "...", "code": "...", "timestamp": "..." } - Never expose stack traces or internal details in error responses
Testing¶
- Unit tests: JUnit 5 + Mockito for service layer
- Integration tests:
@SpringBootTestwith Testcontainers for PostgreSQL - Controller tests:
@WebMvcTestwithMockMvc - Use
@Sqlannotation to load test data from SQL files - Test naming:
should_[expected]_when_[condition]— e.g.,should_throw_not_found_when_customer_missing - Test data builders in
src/test/java/.../fixture/
Git¶
- Conventional commits: feat:, fix:, chore:, refactor:
- One feature per PR, squash merge to main
- Run
./gradlew checkbefore pushing
Do NOT¶
- Do not use field injection (
@Autowiredon fields) - Do not return JPA entities from controllers — always map to DTOs
- Do not use
@Dataor@Getter/@Setteron new code — use records or write accessors - Do not catch
ExceptionorRuntimeExceptionin service code — let the global handler deal with it - Do not use
System.out.println— use SLF4J logging (@Slf4j) - Do not write business logic in controllers — delegate to the service layer ````
Key Sections Explained¶
Architecture — Spring Boot projects have a well-defined package structure. Spelling it out prevents Claude from putting code in the wrong layer.
Coding Conventions — Java 21 records, constructor injection, and the final preference are deliberate choices. Without this section, Claude defaults to older Java patterns with Lombok and field injection.
Database — Flyway migration naming and explicit JPA column annotations prevent the common issue of Claude generating entities that do not match the database schema.
Do NOT — The field injection and entity-in-controller rules catch the two most common Spring Boot mistakes Claude makes. Being explicit about these prevents code review friction.
See Also¶
- CLAUDE.md Setup Guide — how to structure your own CLAUDE.md
- Minimal Example — a simpler starting point
- Python Example — for Python backend projects
- Go Example — for Go microservices