Skip to content

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 resource
  • service/ — business logic, interfaces + implementations
  • repository/ — Spring Data JPA repositories
  • model/ — JPA entity classes
  • dto/ — request/response DTOs (records)
  • mapper/ — MapStruct mappers for entity-DTO conversion
  • config/ — Spring configuration classes
  • exception/ — custom exceptions and global exception handler
  • security/ — Spring Security configuration and filters
  • src/main/resources/
  • application.yml — main config
  • application-local.yml — local dev overrides
  • db/migration/ — Flyway migration scripts
  • src/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 final on 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: CustomerService interface + CustomerServiceImpl
  • All public methods in services have Javadoc
  • Use Optional<T> return types for single-entity lookups — never return null
  • Use lombok only for @Slf4j — do not use @Data, @Getter, @Setter on 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 @Entity with explicit @Table(name = "...") and @Column(name = "...")
  • Always specify column lengths and constraints in the entity
  • Use @Version for optimistic locking on frequently updated entities
  • Repositories extend JpaRepository<T, ID> — add custom query methods with @Query or 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 @Valid on all request body parameters
  • Return DTOs, never entities
  • Use @ResponseStatus for non-200 success codes
  • Paginated endpoints return Page<T> with Spring's Pageable parameter

Exception Handling

Global exception handler in exception/GlobalExceptionHandler.java:

  • @RestControllerAdvice with @ExceptionHandler methods
  • 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: @SpringBootTest with Testcontainers for PostgreSQL
  • Controller tests: @WebMvcTest with MockMvc
  • Use @Sql annotation 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 check before pushing

Do NOT

  • Do not use field injection (@Autowired on fields)
  • Do not return JPA entities from controllers — always map to DTOs
  • Do not use @Data or @Getter/@Setter on new code — use records or write accessors
  • Do not catch Exception or RuntimeException in 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