Spring Framework Core Rules
Dependency injection, bean lifecycle, configuration classes, and profiles.
# CLAUDE.md — Spring Framework Core
## Dependency injection
- **Constructor injection** for required dependencies. Final fields, no `@Autowired` needed when there's a single constructor.
- Field injection (`@Autowired` on a field) is banned. It hides dependencies and breaks immutability.
- Setter injection only for legitimately optional collaborators.
- One bean = one responsibility. Wide constructors are a sign of a bean doing too much.
## Bean configuration
- Java config (`@Configuration` + `@Bean`) over XML. XML is only for legacy.
- Use `@Component` / `@Service` / `@Repository` on classes you own. `@Bean` methods for third-party types and conditional wiring.
- Avoid mixing `@ComponentScan` with manual `@Bean` registration of the same type — the precedence rules surprise you.
## Configuration properties
- `@ConfigurationProperties` over `@Value`. Strongly typed, auto-completed, validated.
- Use records for property classes:
```java
@ConfigurationProperties("app.email")
record EmailProperties(String fromAddress, String smtpHost, int smtpPort) {}
```
- Validate with Bean Validation: `@Validated` on the class, `@NotNull`, `@NotEmpty` on fields.
## Profiles
- One profile per environment: `dev`, `test`, `prod`. Configure with `spring.profiles.active`.
- Don't put production-specific behavior behind a `if` on a profile name. Use `@Profile` on beans.
- `@Profile("!prod")` for "everywhere except prod" beans (dev tools, mock integrations).
## Lifecycle
- `@PostConstruct` for initialization that requires dependency injection to be complete.
- `@PreDestroy` for cleanup. Triggered on graceful shutdown.
- Don't do work in constructors that touches the network, database, or threads. The bean isn't fully wired yet.
## Events
- `ApplicationEventPublisher` for in-process events. Decouples producers from consumers.
- Listeners are `@EventListener` methods. Avoid `ApplicationListener<EventType>` — older API.
- For async listeners, use `@Async` + `@EnableAsync`. Configure the executor explicitly — the default isn't safe for production.
## Transactions
- `@Transactional` on service methods. Don't put it on controllers or repositories.
- Default propagation `REQUIRED` is right 95% of the time.
- `readOnly = true` for queries — Hibernate optimizes flush behavior.
- Self-invocation doesn't trigger the proxy: a `@Transactional` method calling another `@Transactional` method on `this` won't open a new transaction. Inject `self` or split the bean.
## Don't
- Don't inject the `ApplicationContext` to look up beans dynamically. That's service locator anti-pattern.
- Don't `BeanFactory.getBean()` from app code. If you need conditional wiring, use `@Conditional` or factories.
- Don't put initialization in static blocks. Use `@PostConstruct` or `CommandLineRunner`.
- Don't catch and swallow `RuntimeException` to "make Spring happy" — the framework's exception handling is intentional.
Other Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.