Spring Annotations
What each annotation does and the one thing that bites. Grouped by area; scan the table, then follow the link to the long-form page.
Beans, Injection and Scanning
Spring Boot 3 / Spring Framework 6 style (jakarta.* imports). A "bean" is an object the container creates and wires for you.
| Annotation | What it does | Gotcha |
|---|---|---|
@SpringBootApplication | Shortcut for @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan on the main class | Scanning starts at the main class's package; beans in sibling packages above it are invisible |
@Component | Generic stereotype: scanned and registered as a bean | Needs to be under a scanned package; a plain new object is not a bean |
@Service | @Component for business logic (no extra behaviour) | Purely a label; it does not add transactions |
@Repository | @Component for data access; enables persistence exception translation | Translation only works through the proxy, so keep it on the class that talks to the store |
@Controller | MVC controller; return values are view names | Forgetting @ResponseBody on a JSON endpoint gives a "view not found" error |
@RestController | @Controller + @ResponseBody on every method | Cannot return a view name; it returns text |
@Configuration | Class that declares beans with @Bean methods | Full mode (default) proxies the class so @Bean calls return the singleton; proxyBeanMethods = false turns that off |
@Bean | Method return value becomes a bean; name defaults to the method name | Static @Bean methods are needed for BeanFactoryPostProcessor types |
@Autowired | Inject by type (constructor, setter or field) | Prefer constructor injection; a single constructor needs no annotation. Field injection hides dependencies and blocks final |
@Qualifier("name") | Pick one bean when several match the type | Matches bean name or qualifier value, not the class name |
@Primary | Default choice when several beans match | @Qualifier at the injection point wins over @Primary |
@Lazy | Create the bean (or inject a lazy proxy) on first use | Moves startup failures to first request; mask for circular dependencies, not a fix |
@Scope("prototype") | New instance per injection or lookup (default is singleton) | A prototype injected into a singleton is created once; use ObjectProvider to get fresh ones |
@DependsOn | Force bean creation order | Only for hidden dependencies; real ones should be injected |
@Import | Pull in other @Configuration or selector classes | Used by @Enable* annotations; order of imports can matter for overrides |
@ComponentScan | Set packages to scan | A second @ComponentScan overlapping the main one can register beans twice; prefer moving classes under the main package |
@PostConstruct / @PreDestroy | Lifecycle callbacks after injection / before shutdown (jakarta.annotation) | Run once per bean; they are not called for prototype destruction |
@Order / @Priority | Sort injected lists and ordered components (filters, aspects, listeners) | Lower value runs first; it does not control bean creation order |
@EventListener | Method handles an application event | Synchronous by default and in the publisher's transaction |
@TransactionalEventListener | Handle an event at a transaction phase (AFTER_COMMIT default) | Silently ignored if there is no active transaction unless fallbackExecution = true |
Config, Properties, Conditions and Profiles
How values and conditional wiring get into beans. Boot loads application.properties or application.yml, profile variants, env vars and command-line args in a fixed precedence.
| Annotation | What it does | Gotcha |
|---|---|---|
@Value("${key:default}") | Inject one property, with optional default after the colon | No default means startup failure if the key is missing; #{...} is SpEL, ${...} is a placeholder |
@ConfigurationProperties(prefix = "app") | Bind a group of properties to a typed class or record | Needs @EnableConfigurationProperties, @ConfigurationPropertiesScan or @Component to be registered |
@EnableConfigurationProperties(X.class) | Register a properties class as a bean | Not needed if the class is already a component |
@ConstructorBinding | Bind through the constructor (immutable properties) | Boot 3: a single constructor of a record or class is enough; use the annotation only when there are several constructors |
@Validated on properties class | Run Bean Validation on bound properties | Needs spring-boot-starter-validation; put @Valid on nested objects |
@PropertySource("classpath:x.properties") | Add a properties file to the environment | Does not support YAML; lower precedence than Boot's own files |
@Profile("dev") | Bean active only for the named profile | !prod negates; @Profile on a @Configuration class gates all its beans |
@ActiveProfiles("test") | Select profiles in a test class | Replaces, not adds to, spring.profiles.active for that test |
@Conditional(MyCondition.class) | Register bean only if a custom Condition matches | Evaluated while parsing configuration; do not depend on bean state |
@ConditionalOnProperty | Bean only if a property has a value (havingValue, matchIfMissing) | Without matchIfMissing = true a missing property means "off" |
@ConditionalOnClass | Bean only if a class is on the classpath | Use the name attribute (string) on class-level conditions to avoid loading the class |
@ConditionalOnMissingBean | Back off if the user defined a bean of that type | Only reliable in auto-configuration; ordering in normal config is fragile |
@ConditionalOnBean | Bean only if another bean exists | Same ordering fragility; mainly for auto-configuration |
@ConditionalOnWebApplication | Only in servlet or reactive web apps | Use type to pick which |
@ConditionalOnExpression | SpEL-based condition | Hard to debug; prefer @ConditionalOnProperty |
@RefreshScope | Re-create bean on config refresh (Spring Cloud) | Not part of Spring Boot itself; needs Spring Cloud Context |
Controllers, Binding and Error Handling
Mapping annotations pick the handler; parameter annotations say where each argument comes from.
| Annotation | What it does | Gotcha |
|---|---|---|
@RequestMapping | Map URL, method, headers, media types (class or method level) | Without method it matches every HTTP method |
@GetMapping @PostMapping @PutMapping @PatchMapping @DeleteMapping | Shortcuts for @RequestMapping(method = ...) | Class-level path + method-level path are concatenated |
@PathVariable | Value from {id} in the URL | Name must match the placeholder or be set explicitly; type mismatch gives 400 |
@RequestParam | Query or form parameter | required = true by default; Optional or defaultValue relaxes it |
@RequestBody | Deserialise the body (JSON by default) into an object | Malformed JSON gives 400 before your code runs; read once only |
@RequestHeader | One header value | Required by default |
@CookieValue | One cookie value | Missing cookie is a 400 unless required = false |
@ModelAttribute | Bind form fields or query params to an object | Not for JSON bodies; used for form posts |
@ResponseBody | Write the return value to the response body | Already implied by @RestController |
@ResponseStatus(HttpStatus.CREATED) | Fixed status for a method or exception class | Ignored if the method returns ResponseEntity with its own status |
@CrossOrigin | Allow cross-origin calls on a handler | With Spring Security the CORS config must also be enabled in the filter chain |
@ControllerAdvice / @RestControllerAdvice | Global handler for exceptions, binders and model attributes | Applies to all controllers unless you narrow with basePackages or assignableTypes |
@ExceptionHandler(X.class) | Handle an exception type in a controller or advice | The most specific type wins; return a ProblemDetail for RFC 9457 style bodies |
@InitBinder | Customise data binding for a controller | Runs for every request to that controller |
@Valid | Trigger Bean Validation on an argument (jakarta.validation) | Failure throws MethodArgumentNotValidException for bodies; nested objects need @Valid too |
@Validated | Spring variant: validation groups, and method validation on a class | Class-level @Validated enables @NotNull etc. on method parameters through a proxy |
@NotNull @NotBlank @Size @Min @Max @Email @Pattern | Constraint annotations on fields or parameters | @NotNull allows empty string; @NotBlank is for strings only; @NotEmpty for collections |
@RestClientTest | Slice test for RestClient or RestTemplate code | Combine with MockRestServiceServer |
JPA, Spring Data and @Transactional
Transactions are applied by a proxy around the bean. That one fact explains most of the surprises below.
| Annotation | What it does | Gotcha |
|---|---|---|
@Transactional | Run the method in a transaction (commit on return, roll back on exception) | Only calls coming through the proxy count; see the self-invocation row |
propagation | How to join or create (REQUIRED default, REQUIRES_NEW, NESTED, SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER) | REQUIRES_NEW takes a second connection; deadlocks with the outer transaction on the same row |
isolation | Isolation level (DEFAULT uses the database default) | Applies only when a new transaction starts (REQUIRED or REQUIRES_NEW) |
readOnly = true | Hint: no writes expected (can skip flush, route to replicas) | A hint, not a guard; the driver or database may still allow writes |
timeout | Seconds before the transaction is marked for rollback | Applies only to new transactions; not a statement timeout |
rollbackFor / noRollbackFor | Choose which exceptions roll back | Default: RuntimeException and Error roll back, checked exceptions commit |
| Self-invocation | this.other() inside the same bean skips the proxy | The inner @Transactional has no effect; move it to another bean or use AspectJ mode |
| Visibility | Class-based proxies can handle protected and package-visible methods since 6.0 | Interface-based proxies need public methods declared on the interface; private is never advised |
final class or method | CGLIB cannot subclass or override them | Advice (transactions, cache, async) silently does nothing |
| Checked exceptions | Not rolled back by default | Set rollbackFor = Exception.class or the global rollbackOn option (Spring 6.2+) |
@Entity @Table @Id | Map a class to a table and mark the primary key (jakarta.persistence) | Entities need a no-arg constructor (can be protected); avoid final |
@GeneratedValue | Database-generated key (IDENTITY, SEQUENCE, ...) | IDENTITY disables JDBC batch inserts; Oracle and PostgreSQL work well with SEQUENCE |
@Column @Enumerated(EnumType.STRING) | Column details; store enums by name | Default ORDINAL breaks when the enum order changes |
@OneToMany @ManyToOne @ManyToMany | Relationships; mappedBy marks the inverse side | @OneToMany is lazy, @ManyToOne is eager by default; watch N+1 selects |
@Version | Optimistic locking column | Update fails with OptimisticLockingFailureException on conflict; retry at the caller |
@Query("...") | Custom JPQL (or native with nativeQuery = true) on a repository method | Modifying queries also need @Modifying and a transaction |
@Modifying | Mark an @Query as UPDATE or DELETE | Bypasses the persistence context; clearAutomatically = true avoids stale entities |
@EntityGraph | Fetch associations in one query for a repository method | Fixes N+1 but can multiply rows for collections |
@Lock(LockModeType.PESSIMISTIC_WRITE) | Lock rows read by a repository method | Needs a transaction; lock timeouts are database specific |
@EnableJpaRepositories | Turn on repository scanning | Boot does it for you; add it only for custom base packages |
@EnableTransactionManagement | Turn on @Transactional processing | Boot auto-configures it; has mode and rollbackOn (6.2+) attributes |
Proxy-Based Features
All of these work through proxies, so they share the same limits: no self-invocation, no private, no final.
| Annotation | What it does | Gotcha |
|---|---|---|
@Aspect + @Component | Declare an aspect | @Aspect alone does not register a bean |
@Pointcut("execution(...)") | Reusable pointcut expression | Matches only Spring beans, and only method execution |
@Before @After @AfterReturning @AfterThrowing | Advice at fixed points | @After runs on both outcomes (like finally) |
@Around | Wrap the call; you decide whether to call proceed() | Forget proceed() and the target never runs; return its result |
@EnableAspectJAutoProxy | Enable @Aspect processing | Boot enables it when spring-boot-starter-aop is present |
@EnableCaching | Turn on cache annotations | Without it, @Cacheable is silently ignored |
@Cacheable("name") | Return a cached value, else call and store | Key defaults to the arguments; null results are cached unless unless = "#result == null" |
@CachePut | Always run, then update the cache | Do not combine with @Cacheable on the same method |
@CacheEvict | Remove entries (allEntries = true clears the cache) | beforeInvocation = false default: a failing method leaves the cache untouched |
@Caching | Combine several cache annotations | Rarely needed; readability suffers |
@EnableScheduling | Turn on @Scheduled | Without it, nothing runs and nothing complains |
@Scheduled(fixedRate / fixedDelay / cron) | Run periodically | fixedRate can overlap if a run is slow with a multi-thread scheduler; default scheduler pool has one thread; runs on every instance |
@EnableAsync | Turn on @Async | Without it, the method runs on the caller thread |
@Async | Run the method on an executor, return void or CompletableFuture | Exceptions from void methods go to the AsyncUncaughtExceptionHandler; the security and transaction contexts do not follow the thread |
@Retryable / @Recover | Retry a method (needs the spring-retry library and @EnableRetry) | Retry support is changing between Spring generations; check the reference for your Framework version before relying on the artifact |
Spring Security Annotations
Spring Security 6 style: a SecurityFilterChain bean for URL rules, annotations for method rules.
| Annotation | What it does | Gotcha |
|---|---|---|
@EnableWebSecurity | Turn on web security configuration | Boot applies it automatically when Spring Security is on the classpath |
@EnableMethodSecurity | Turn on method security; replaces @EnableGlobalMethodSecurity | @PreAuthorize family is on by default; @Secured needs securedEnabled = true, JSR-250 needs jsr250Enabled = true |
@PreAuthorize("hasRole('ADMIN')") | Check before the method runs (SpEL) | hasRole adds the ROLE_ prefix, hasAuthority does not |
@PostAuthorize | Check after the method returns (can use returnObject) | The method has already run, including side effects |
@PreFilter / @PostFilter | Filter collection arguments or results | Filters in memory after loading everything; slow for big collections |
@Secured("ROLE_X") | Older role check | No SpEL; must write the full ROLE_ name |
@AuthenticationPrincipal | Inject the current principal into a controller method | Type must match what your UserDetailsService returns |
@WithMockUser | Fake an authenticated user in a test | Does not run your filters or JWT validation |
Test Slices and Test Beans
@SpringBootTest loads everything; slices load only one layer and run faster.
| Annotation | What it does | Gotcha |
|---|---|---|
@SpringBootTest | Load the full application context | Default webEnvironment = MOCK starts no server; use RANDOM_PORT for real HTTP |
@WebMvcTest(X.class) | Only MVC beans: controllers, advice, filters | Services and repositories are not loaded; supply them with @MockitoBean. Boot 4 moved slices into per-technology modules (for example spring-boot-webmvc-test), so imports change when upgrading |
@DataJpaTest | JPA slice: entities, repositories, transaction rollback per test | Replaces your DataSource with an embedded database unless you set @AutoConfigureTestDatabase(replace = NONE) |
@JsonTest | Test JSON serialisation only | Uses the auto-configured ObjectMapper |
@MockitoBean | Replace or add a Mockito mock bean in the context (Spring Framework test annotation) | Replaces @MockBean, which is deprecated since Boot 3.4; mocks skip AOP proxies, so @Transactional and @Cacheable do not apply to them |
@MockitoSpyBean | Wrap an existing bean in a Mockito spy; replaces @SpyBean | Needs exactly one candidate bean; cannot spy on scoped proxies |
@MockBean / @SpyBean | Boot's older equivalents | Deprecated since 3.4 in favour of the two Framework annotations above; not exact drop-in replacements |
@TestConfiguration | Extra beans for a test only | Not picked up by component scanning of the main app when nested |
@DynamicPropertySource | Set properties from a runtime value (Testcontainers port) | Method must be static |
@ServiceConnection | Wire a Testcontainers container to auto-configuration without properties | Boot 3.1+; works with supported container types only |
@Sql("/data.sql") | Run a script before a test | Runs inside the test transaction unless configured otherwise |
@DirtiesContext | Throw away the cached context after the test | Very slow when overused; each different config creates another cached context |
@TestPropertySource | Inline or file properties for a test | Highest precedence among property sources |
@AutoConfigureMockMvc | Add MockMvc to a @SpringBootTest | Not needed with @WebMvcTest |
Kafka and RabbitMQ
Listener annotations turn a method into a message consumer. Boot auto-configures the listener infrastructure when the client libraries are present.
| Annotation | What it does | Gotcha |
|---|---|---|
@EnableKafka | Process @KafkaListener (Boot auto-configures it) | Needed only in non-Boot apps |
@KafkaListener(topics = "t", groupId = "g") | Consume records from a topic | Consumers sharing a groupId split the partitions; concurrency above the partition count is wasted |
@KafkaListener(topicPartitions = ...) | Assign specific partitions and offsets | Bypasses group rebalancing for those partitions |
@RetryableTopic | Non-blocking retry topics and a dead-letter topic | Changes topic names and ordering guarantees |
@KafkaHandler | Method-level routing on a class-level @KafkaListener | Only one isDefault handler allowed |
@EnableRabbit | Process @RabbitListener (Boot auto-configures it) | Needed only in non-Boot apps |
@RabbitListener(queues = "q") | Consume messages from a queue | Default acknowledge mode is automatic; an uncaught exception causes requeue and can loop forever without a retry or DLQ policy |
@RabbitHandler | Method-level routing on a class-level @RabbitListener | Chosen by payload type |
@SendTo | Send the return value to another destination | Needs a reply template or address configured |
Spring Annotation Mistakes
- **
@Transactionalon a method called from the same class.** No proxy, no transaction. Move the method to another bean. - **
@Transactionalonprivate,finalor static methods.** Silently ignored. Same for@Async,@Cacheable,@Scheduledadvice. - **Forgetting
@EnableCaching,@EnableSchedulingor@EnableAsync.** The annotation compiles and does nothing. - Expecting checked exceptions to roll back. They commit by default; set
rollbackFor. - **Mixing
@MockBeanand@MockitoBeanin one project.** Pick one; on Boot 3.4+ use@MockitoBean. - Field injection everywhere. Hides dependencies and makes unit tests need a container.
- **
@Valuefor a whole config group.** Use@ConfigurationProperties; it is typed and validated. - **
@Scheduledon a clustered service.** It runs on every instance; add a lock (for example ShedLock) or run it on one node. - **
@Asyncvoid methods that throw.** The caller never sees the error; return aCompletableFuture. - **
javax.*imports on Boot 3.** Boot 3 usesjakarta.*; mixed imports cause confusing "no such bean" or binding errors.
Long-form pages and what was checked
| Topic | Page |
|---|---|
| Core container, beans and wiring | Spring Core, Beans |
| Configuration and properties | Configuration |
| REST controllers, validation, errors | REST API, Validation & Errors |
| Transactions and JPA | Transactions, Data JPA |
| AOP, caching, resilience | AOP, Caching & Resilience |
| Security and JWT | Security, JWT & OAuth2 |
| Testing | Testing |
| Kafka and RabbitMQ with Spring | Kafka with Spring, RabbitMQ |
Checked on 2026-10-31: @Transactional defaults, attributes, visibility rules and the 6.2 rollbackOn option against the Spring Framework reference (data-access/transaction/declarative/annotations); @EnableMethodSecurity replacing @EnableGlobalMethodSecurity and the securedEnabled / jsr250Enabled opt-ins against the Spring Security reference; @MockBean / @SpyBean deprecation in Boot 3.4 against the Boot 3.4 release notes; @MockitoBean behaviour (AOP proxies skipped, singleton conversion, REPLACE_OR_CREATE default) and the @SpringBootTest default MOCK environment against the current Boot and Framework testing docs. Not re-checked line by line: the Spring Kafka and Spring AMQP listener rows and the JPA mapping rows, which follow the long-form pages.