A Spring Boot annotation is metadata on a class, method or parameter that tells Spring what to create, wire, expose or intercept, so you write behaviour and not boilerplate. This page lists the annotations you will meet in a real Boot 4 project, grouped by job, then spends its length on the pairs people confuse. The comparisons below were run, not remembered.
Tested on Spring Boot 4.1.1 (Spring Framework 7.0.9, Tomcat 11.0.24) with OpenJDK 21.0.12. The demo app is four files: DemoApplication.java, Components.java, More.java and Report.java. Each output block below comes from running it.
Three facts explain half of the list. First, almost every annotation is a meta-annotation built from smaller ones. The demo prints this at startup:
REPORT meta-annotations of @SpringBootApplication:
REPORT SpringBootConfiguration
REPORT EnableAutoConfiguration
REPORT ComponentScan
REPORT meta-annotations of @RestController:
REPORT Controller
REPORT ResponseBody
REPORT @Service is a @Component: true
REPORT @Repository is a @Component: true
REPORT @RestController is a @Component: trueSo @SpringBootApplication is three annotations, @RestController is two, and @Service, @Repository and @RestController are all @Components. Source: Report.java.
The annotation list, by job
Core and stereotypes
| Annotation | What it does |
|---|---|
@Component | Marks a class as a Spring-managed bean found by component scanning. |
@Service | A @Component for business logic. No extra behaviour, only intent. |
@Repository | A @Component for data access; enables persistence exception translation. |
@Controller | A web @Component whose methods return view names. |
@RestController | @Controller + @ResponseBody: return values are written to the response body. |
@Configuration | A class that declares beans with @Bean methods. |
@Bean | A method whose return value becomes a bean. Use it for classes you do not own. |
@Import | Pulls other configuration classes into this one. |
@ComponentScan | Tells Spring which packages to scan for stereotypes. |
Dependency injection and bean control
| Annotation | What it does |
|---|---|
@Autowired | Inject a dependency by type. Optional on a single constructor. |
@Qualifier | Pick one bean by name when several match. |
@Primary | Make one bean the default when several match. |
@Value | Inject a single property or SpEL expression. |
@Scope | Set the bean scope: singleton (default), prototype, request, session. |
@Lazy | Create the bean on first use instead of at startup. |
@Profile | Register the bean only when a profile is active. |
@Conditional | Register the bean only if a custom condition holds. |
@PostConstruct | Run a method after injection (jakarta.annotation). |
@PreDestroy | Run a method before the bean is destroyed (jakarta.annotation). |
Spring Boot
| Annotation | What it does |
|---|---|
@SpringBootApplication | @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan (shown in the output below). |
@EnableAutoConfiguration | Turns on Boot’s classpath-driven auto-configuration. |
@ConfigurationProperties | Bind a group of properties (a prefix) to a typed class or record. |
@EnableConfigurationProperties | Register a @ConfigurationProperties class as a bean. |
@ConfigurationPropertiesScan | Scan for @ConfigurationProperties classes instead of listing them. |
@ConditionalOnProperty | Register the bean only if a property has a given value. |
@ConditionalOnClass | Register the bean only if a class is on the classpath. |
@ConditionalOnMissingBean | Register the bean only if you have not defined your own. |
Web
| Annotation | What it does |
|---|---|
@RequestMapping | Map a URL (and method) to a handler; also usable on a class for a prefix. |
@GetMapping | Shortcut for @RequestMapping(method = GET). Likewise @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping. |
@PathVariable | Bind a part of the URL path: /items/{id}. |
@RequestParam | Bind a query parameter or form field: ?style=bold. |
@RequestBody | Convert the request body (usually JSON) to an object. |
@RequestHeader | Bind a request header. |
@CookieValue | Bind a cookie. |
@ResponseBody | Write the return value to the response instead of resolving a view. |
@ResponseStatus | Set the HTTP status of a handler or exception. |
@ExceptionHandler | Handle an exception thrown by a controller. |
@RestControllerAdvice | Global @ExceptionHandlers for all controllers (@ControllerAdvice + @ResponseBody). |
@CrossOrigin | Allow cross-origin requests to a handler or controller. |
@Valid | Trigger Bean Validation on an argument (jakarta.validation). |
@Validated | Spring’s variant: validation groups, and validation of @ConfigurationProperties and method parameters. |
Data and transactions
| Annotation | What it does |
|---|---|
@Transactional | Run a method in a transaction through a proxy. |
@Entity | Marks a JPA entity (jakarta.persistence). |
@Id | The primary key of an entity. Pair with @GeneratedValue. |
@Column | Customise a column mapping. |
@OneToMany / @ManyToOne | Relationship mappings between entities. |
@Query | Write the query for a Spring Data repository method. |
Scheduling, async, caching, events, resilience
| Annotation | What it does |
|---|---|
@EnableScheduling / @Scheduled | Run a method on a fixed rate, delay or cron. |
@EnableAsync / @Async | Run a method on another thread. |
@EnableCaching / @Cacheable | Cache a method’s result. @CacheEvict removes entries, @CachePut refreshes them. |
@EventListener | Receive an application event. @TransactionalEventListener waits for the transaction phase. |
@Retryable / @ConcurrencyLimit | Retry and concurrency limits built into Spring Framework 7. |
Security and testing
| Annotation | What it does |
|---|---|
@EnableMethodSecurity | Turns on @PreAuthorize and @PostAuthorize checks on methods. |
@SpringBootTest | Start the full application context in a test. |
@WebMvcTest | Start only the web layer for controller tests. |
@DataJpaTest | Start only the JPA layer for repository tests. |
@MockitoBean | Replace a bean with a Mockito mock in the test context (replaces @MockBean in Boot 4). |
@ActiveProfiles | Choose the profiles for a test. |
The next sections are for the annotations that look the same and are not. Every example is in the demo files linked above.
Commonly confused pairs, with real output
@Component vs @Bean
@Component goes on your class and component scanning finds it. @Bean goes on a method inside a @Configuration class and registers whatever the method returns, which is how you register a class you cannot edit. The demo has both: EmailNotifier is a @Component, and AppConfig.appName() is a @Bean. The beans from the demo package:
REPORT beans from package demo:
REPORT app-demo.AppProps -> AppProps
REPORT appConfig -> AppConfig$$SpringCGLIB$$0
REPORT demoApplication -> DemoApplication
REPORT emailNotifier -> EmailNotifier
REPORT itemController -> ItemController
REPORT itemRepository -> ItemRepository
REPORT itemService -> ItemService
REPORT propsConfig -> PropsConfig$$SpringCGLIB$$0
REPORT report -> Report
REPORT sms -> SmsNotifier
REPORT validController -> ValidController
REPORT viewController -> ViewControllerNote appConfig is a $$SpringCGLIB$$ subclass: Spring proxies @Configuration classes so @Bean methods return the same singleton on repeated calls. Source: Components.java.
@Controller vs @RestController
A @Controller method returns a view name. A @RestController method returns the body. Returning a String from @Controller in an app with no view technology gives a 404, which is the usual confusion:
$ curl localhost:8080/view (@Controller returning a String)
{"timestamp":"2026-10-21T16:04:04.735Z","status":404,"error":"Not Found","path":"/view"}
HTTP 404The method returned "items", Spring looked for a view called items, found none, and answered Not Found. Add @ResponseBody or switch to @RestController. Source: More.java.
@Primary vs @Qualifier
Two beans implement Notifier. @Primary on EmailNotifier makes it the default; @Qualifier("sms") on a parameter overrides that for one injection point. Same controller, both used:
$ curl localhost:8080/api/notify?msg=hi
{"qualified":"sms:hi","primary":"email:hi"}Without either, startup fails. This is the real failure when two Greeter beans exist and nothing says which to use:
Parameter 0 of constructor in demo.NeedsGreeter required a single bean, but 2 were found:
- enGreeter: defined in URL [<jar>/demo/EnGreeter.class]
- frGreeter: defined in URL [<jar>/demo/FrGreeter.class]
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumedThe error names both candidates and the three ways out. Source: More.java. More on resolution: @Autowired Explained: By-Type Resolution, @Qualifier, @Primary, ObjectProvider and List Injection.
@Value vs @ConfigurationProperties
@Value("${app.greeting}") injects one property. @ConfigurationProperties(prefix = "app") binds the whole app.* group to a record and validates it (here @NotBlank and @Size(max = 40)). Both read the same file:
$ curl localhost:8080/api/config
{"props.maxItems":5,"value":"Hello from properties","props.greeting":"Hello from properties"}Use @Value for a one-off; use @ConfigurationProperties when properties belong together or need validation. One surprise from the bean listing above: the properties record is registered under the name app-demo.AppProps, prefix first. Source: Components.java. Deeper: @ConfigurationProperties vs @Value in Spring Boot 4: Binding, Validation and Relaxed Rules.
@PathVariable vs @RequestParam
@PathVariable reads from the path template, @RequestParam from the query string. One handler uses both:
$ curl localhost:8080/api/items/1?style=bold
bold:book
$ curl localhost:8080/api/items/abc
{"timestamp":"2026-10-21T16:04:04.715Z","status":400,"error":"Bad Request","path":"/api/items/abc"}
HTTP 400A non-numeric path variable is a 400, not a 500: the conversion failure is mapped for you. Source: Components.java.
@Valid and what the client sees
@Valid @RequestBody NewItem triggers the constraints on the record (@NotBlank name, @Min(1) qty). A bad body is rejected before your method runs:
$ curl -XPOST -H "Content-Type: application/json" -d {"name":"","qty":0} localhost:8080/api/new
{"timestamp":"2026-10-21T16:04:04.682Z","status":400,"error":"Bad Request","path":"/api/new"}
HTTP 400
$ curl -XPOST -H "Content-Type: application/json" -d {"name":"cup","qty":2} localhost:8080/api/new
{"created":"cup","qty":2}
HTTP 200Boot’s default 400 body says nothing about which field failed. If clients need that, return a ProblemDetail: Global Exception Handling with ProblemDetail (RFC 9457) in Spring Boot 4. Source: More.java.
@Profile
DevOnly is annotated @Profile("dev"). With no active profile it is not created:
REPORT DevOnly (@Profile("dev")) present: falseProfiles in depth: Spring Boot Profiles Done Right: Config Import, Config Trees and Kubernetes ConfigMaps.
Where to go next
This list is a map. For the order to learn it in, use the Spring Boot tutorial for beginners; for what each version of Boot changed, Spring Boot versions; and for how Boot differs from the framework underneath, Spring vs Spring Boot vs Spring MVC.
Common interview questions
What is the difference between @Component, @Service and @Repository?
All three register a bean. @Service and @Controller add only meaning; @Repository also enables exception translation for data-access errors.
What does @SpringBootApplication do?
It combines @SpringBootConfiguration, @EnableAutoConfiguration and @ComponentScan.
What is the difference between @Controller and @RestController?
@RestController is @Controller plus @ResponseBody, so return values become the response body.
@Bean or @Component?
@Component for your own classes, @Bean for third-party classes or when construction needs logic.
How do you resolve two beans of the same type?
Mark one @Primary, qualify the injection point with @Qualifier, or inject a List of all of them.
Common mistakes
- Using
@Controllerand expecting JSON. You get a 404 or a view lookup (shown above). - Putting
@Transactionalor@Asyncon a method called from the same class. The proxy is bypassed. See @Transactional in Spring: Propagation, Isolation, and the Six Ways It Silently Does Nothing and @Async in Spring Boot 4: Executors, Virtual Threads and the Self-Invocation Trap. - Forgetting the enabling annotation.
@Scheduledneeds@EnableScheduling;@Cacheableneeds@EnableCaching. - Field injection with
@Autowired. Constructor injection is testable and lets fields befinal. See Dependency Injection in Spring Boot 4: Constructor vs Setter vs Field (and Why Field Injection Hurts). - Still writing
@MockBeanin Boot 4. Use@MockitoBean.
FAQ
What are the most important Spring Boot annotations?
@SpringBootApplication, @RestController, @Service, @Repository, @Autowired, @GetMapping/@PostMapping, @RequestBody, @Transactional, @ConfigurationProperties and @Bean.
Is @Autowired required on a constructor?
No. With one constructor, Spring injects it without the annotation.
What changed for annotations in Spring Boot 4?
@MockBean and @SpyBean are replaced by @MockitoBean and @MockitoSpyBean; see the versions page and the Boot 3 to 4 migration guide.
Further reading
- Components.java, More.java, Report.java, DemoApplication.java
- Spring Boot tutorial for beginners
- Spring vs Spring Boot vs Spring MVC
- Top 50 Spring Boot 4 interview questions
- Dependency Injection in Spring Boot 4: Constructor vs Setter vs Field (and Why Field Injection Hurts)
- @ConfigurationProperties vs @Value in Spring Boot 4: Binding, Validation and Relaxed Rules
- Top 40 Java Collections interview questions
- Top 40 Java concurrency interview questions
- Spring Boot reference documentation
No Comments yet!