Core Spring & Spring Boot Annotations

This section documents Spring Boot 4.1.x and Spring Framework 7.0.x on the Java 17/21+ baseline, as described by the official Spring Boot reference documentation and each sub-project’s own site (Spring Data, Spring for Apache Kafka, Micrometer, Project Reactor, springdoc-openapi, Spring gRPC, and the others listed in this section’s Bibliography) — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Spring Boot and its ecosystem continue to evolve; the examples here target the current 4.1.x / 7.0.x releases.

This section’s bibliography lists the reference material consulted while preparing these pages.

@Component, @Service, @Repository, @Controller, @RestController, and @Configuration all look interchangeable — component scanning picks up any of them the same way — but they are not functionally identical. Some add real container behavior beyond "register this as a bean"; others add nothing beyond documentation intent. This page explains exactly what each one contributes, then catalogs the rest of the annotations this reference relies on, grouped by concern, with a pointer to the page that covers each in depth.

The stereotype hierarchy

Every stereotype annotation is itself meta-annotated with @Component, which is why component scanning treats them uniformly — but the framework (and, in `@Repository’s case, the container’s post-processing machinery) treats some of them specially:

flowchart TD Component["@Component\n(base stereotype -- no extra behavior)"] Service["@Service\nadds: nothing beyond @Component today --\nsemantic marker for business/orchestration logic"] Repository["@Repository\nadds: automatic persistence-exception\ntranslation into DataAccessException"] Controller["@Controller\nadds: participation in Spring MVC's\nrequest-mapping / view-resolution"] RestController["@RestController\n= @Controller + @ResponseBody --\nadds: return values serialized straight\nto the response body"] Configuration["@Configuration\nadds: CGLIB proxying of @Bean methods\n(singleton semantics on inter-bean calls)"] Component --> Service Component --> Repository Component --> Controller Controller --> RestController Component --> Configuration

@Component — the base stereotype

The generic marker: a class annotated @Component is discovered by component scanning and registered as a bean. Every other stereotype below is meta-annotated with @Component, so nothing about how the container finds the class differs between them.

@Component
public class RequestIdGenerator {
    // a generic bean -- no business/persistence/web-layer semantics apply
}

@Service — a semantic marker, not (yet) extra behavior

@Service is a specialization of @Component intended for business/orchestration logic. As of the current Spring Framework, it adds no container behavior beyond @Component — the two are functionally identical to the container. Its value is documentation: it tells a reader (and any pointcut expression targeting execution(* ..Service.(..)), the way core-concepts.adoc’s `TimingAspect example does) which classes carry business logic, distinct from data access or web dispatch. Spring’s own Javadoc for @Service notes this is a general-purpose stereotype that individual teams may narrow further.

@Repository — automatic persistence-exception translation

@Repository is the one stereotype that changes runtime behavior. When a PersistenceExceptionTranslationPostProcessor bean is present in the context — Spring Boot registers one automatically as soon as a PersistenceExceptionTranslator bean exists, which auto-configuration provides for JPA/Hibernate, JDBC, and several NoSQL modules — every @Repository-annotated bean is wrapped in a proxy that intercepts native, technology-specific exceptions (a JPA PersistenceException, a Hibernate exception, a raw SQLException) and translates them into Spring’s unified, unchecked org.springframework.dao.DataAccessException hierarchy:

@Repository
public class JdbcOrderRepository implements OrderRepository {

    private final JdbcClient jdbcClient;

    public JdbcOrderRepository(JdbcClient jdbcClient) {
        this.jdbcClient = jdbcClient;
    }

    public Order save(Order order) {
        // a unique-constraint violation here surfaces to callers as
        // DataIntegrityViolationException, not a driver-specific SQLException --
        // only because this class is annotated @Repository
        return jdbcClient.sql("INSERT INTO orders (...) VALUES (...)")
                .update() > 0 ? order : null;
    }
}

The practical consequence: calling code can catch DataAccessException (or a specific subtype, like DataIntegrityViolationException or OptimisticLockingFailureException) without importing JPA/Hibernate/JDBC-specific exception types, and that catch block keeps working if the underlying persistence technology is ever swapped. This translation only applies to a bean actually annotated @Repository — a plain @Component making the same database calls does not get it. Spring Data repository interfaces (JpaRepository, MongoRepository, etc., covered on Spring Data Overview) carry this behavior implicitly, since their generated proxies are registered the same way.

The DataAccessException hierarchy

Every exception translated by @Repository lands somewhere in one unchecked hierarchy rooted at org.springframework.dao.DataAccessException. The two second-level branches carry real semantic meaning worth knowing before the leaf exceptions: NonTransientDataAccessException means retrying the exact same operation will fail again until something about the request or the data changes; TransientDataAccessException means the same operation might succeed on a plain retry, with no code change needed.

Exception Extends Thrown when

DataAccessException

NestedRuntimeException (the hierarchy’s root)

Never thrown directly — catch this to handle any data-access failure regardless of technology.

NonTransientDataAccessException

DataAccessException

Never thrown directly — the abstract parent for failures a retry alone won’t fix.

DataIntegrityViolationException

NonTransientDataAccessException

A write violated a constraint — NOT NULL, a foreign key, a CHECK constraint.

DuplicateKeyException

DataIntegrityViolationException

Specifically a unique-index or primary-key violation (inserting/updating to a value that already exists).

DataRetrievalFailureException

NonTransientDataAccessException

Data that was expected could not be retrieved correctly.

IncorrectResultSizeDataAccessException

DataRetrievalFailureException

A query expected to return one shape of result returned a different number of rows (e.g. findOne-style lookup that matched more than one row).

EmptyResultDataAccessException

IncorrectResultSizeDataAccessException

A query expected at least one result (e.g. findById(…​).orElseThrow()-style access via a raw API) but got zero rows back.

InvalidDataAccessApiUsageException

NonTransientDataAccessException

The data access API itself was used incorrectly in Java code (e.g. building an invalid query object) — a programming error, not a data problem.

InvalidDataAccessResourceUsageException

NonTransientDataAccessException

The underlying resource was used incorrectly — most commonly a SQL syntax error (surfaced more specifically as JDBC’s BadSqlGrammarException, itself a subclass of this one).

NonTransientDataAccessResourceException

NonTransientDataAccessException

The resource itself is unusable in a way retrying won’t fix (e.g. cannot connect to the database at all —  wrong URL/credentials).

PermissionDeniedDataAccessException

NonTransientDataAccessException

The current user/connection lacks permission to perform the requested operation.

UncategorizedDataAccessException

NonTransientDataAccessException

Spring’s exception translator recognized this as a data-access failure but could not classify it any more specifically — the fallback bucket.

TransientDataAccessException

DataAccessException

Never thrown directly — the abstract parent for failures where a plain retry might just work.

ConcurrencyFailureException

TransientDataAccessException

Never thrown directly — the abstract parent for both locking-failure branches below.

OptimisticLockingFailureException

ConcurrencyFailureException

An application-level version check (@Version, covered on Spring Data Overview) found the record had already been modified since it was read — not detected by the database itself.

ObjectOptimisticLockingFailureException

OptimisticLockingFailureException

The specific @Version-checked object that failed, with its persistent class and identifier attached — this is exactly what Spring Data throws for an optimistic-lock conflict (its own JpaOptimisticLockingFailureException narrows this further for JPA specifically).

PessimisticLockingFailureException

ConcurrencyFailureException

A real database-level locking violation was reported by the driver (e.g. a SELECT …​ FOR UPDATE conflict).

CannotAcquireLockException

PessimisticLockingFailureException

A lock could not be acquired, typically because a lock-wait timeout elapsed.

DeadlockLoserDataAccessException

PessimisticLockingFailureException

The current transaction was chosen as the "victim" and rolled back to resolve a database deadlock.

CannotSerializeTransactionException

PessimisticLockingFailureException

A transaction running at SERIALIZABLE isolation could not be serialized against concurrent transactions.

QueryTimeoutException

TransientDataAccessException

A query exceeded a configured timeout before completing.

TransientDataAccessResourceException

TransientDataAccessException

The resource failed in a way that is likely temporary (e.g. a dropped connection that a fresh one would fix).

RecoverableDataAccessException

DataAccessException

A previously failing operation might succeed once some recovery step (e.g. failing over to a different node) has taken place.

flowchart BT DAE["DataAccessException\n(the hierarchy's root)"] NTDAE["NonTransientDataAccessException\n(retry alone won't help)"] -->|extends| DAE DIVE["DataIntegrityViolationException\n(constraint violated)"] -->|extends| NTDAE DKE["DuplicateKeyException\n(unique/PK violation)"] -->|extends| DIVE DRFE["DataRetrievalFailureException\n(couldn't retrieve as expected)"] -->|extends| NTDAE IRSDAE["IncorrectResultSizeDataAccessException\n(wrong row count)"] -->|extends| DRFE ERDAE["EmptyResultDataAccessException\n(expected ≥1 row, got 0)"] -->|extends| IRSDAE IDAAUE["InvalidDataAccessApiUsageException\n(API misused in Java code)"] -->|extends| NTDAE IDARUE["InvalidDataAccessResourceUsageException\n(resource misused, e.g. bad SQL)"] -->|extends| NTDAE NTDARE["NonTransientDataAccessResourceException\n(resource unusable)"] -->|extends| NTDAE PDDAE["PermissionDeniedDataAccessException\n(insufficient permissions)"] -->|extends| NTDAE UDAE["UncategorizedDataAccessException\n(fallback bucket)"] -->|extends| NTDAE TDAE["TransientDataAccessException\n(retry might succeed)"] -->|extends| DAE CFE["ConcurrencyFailureException\n(concurrent modification detected)"] -->|extends| TDAE OLFE["OptimisticLockingFailureException\n(app-level version check failed)"] -->|extends| CFE OOLFE["ObjectOptimisticLockingFailureException\n(the specific @Version conflict Spring Data throws)"] -->|extends| OLFE PLFE["PessimisticLockingFailureException\n(DB-level lock violation)"] -->|extends| CFE CALE["CannotAcquireLockException\n(lock-wait timeout)"] -->|extends| PLFE DLDAE["DeadlockLoserDataAccessException\n(deadlock victim)"] -->|extends| PLFE CSTE["CannotSerializeTransactionException\n(SERIALIZABLE isolation conflict)"] -->|extends| PLFE QTE["QueryTimeoutException\n(query exceeded timeout)"] -->|extends| TDAE TDARE["TransientDataAccessResourceException\n(temporary resource failure)"] -->|extends| TDAE RDAE["RecoverableDataAccessException\n(may succeed after recovery)"] -->|extends| DAE

See the DataAccessException Javadoc and Spring Framework — DAO Support for the complete hierarchy (including a handful of R2DBC- and script-execution-specific subclasses not listed above) and the exception-translation mechanism’s design rationale.

@Controller and @RestController — the web-layer stereotypes

@Controller marks a class as a Spring MVC controller: its handler methods participate in request mapping, and a returned String is resolved as a logical view name by default (the traditional server-rendered-page model, covered on Server-Side Web UI Frameworks). @RestController is @Controller plus @ResponseBody folded in as a single meta-annotation, so every handler method’s return value is serialized directly to the response body (JSON by default) instead of being resolved to a view — the standard shape for the REST APIs covered on REST APIs.

@Configuration — full mode, lite mode, and CGLIB proxying

A @Configuration class is CGLIB-subclassed at startup ("full" mode, the default) so that one @Bean method calling another @Bean method within the same class returns the container’s existing singleton instead of a fresh object — the proxy intercepts the call and checks the container first:

@Configuration // proxyBeanMethods defaults to true: "full" mode
public class ClientConfig {

    @Bean
    public Clock systemClock() {
        return Clock.systemUTC();
    }

    @Bean
    public AuditLogger auditLogger() {
        // calling systemClock() here does NOT construct a second Clock --
        // the CGLIB proxy intercepts the call and returns the one
        // already-registered systemClock bean
        return new AuditLogger(systemClock());
    }
}

A class carrying @Bean methods without being itself @Configuration (or one explicitly opting out via @Configuration(proxyBeanMethods = false), "lite" mode) is not proxied: an inter-method call like systemClock() above becomes a plain Java method call that constructs a brand-new object every time, which is almost always a bug if the bean is meant to be a shared singleton. proxyBeanMethods = false trades that safety net for faster startup and lower memory (no CGLIB subclass generated) — appropriate when a configuration class’s @Bean methods never call each other, and the default choice for GraalVM native-image builds.

Annotation reference: what’s covered where

Beyond the stereotypes above, this reference relies on a large surface of other core annotations. Rather than re-explain each in place, this table groups them by concern and points to the page with the full treatment:

Category Annotations Covered in depth on

Dependency injection

@Autowired, @Qualifier, @Primary, @Value, @Lazy

Core Concepts

Bean definition & lifecycle

@Bean, @Scope, @DependsOn, @PostConstruct, @PreDestroy

Core Concepts

Configuration & profiles

@ConfigurationProperties, @EnableConfigurationProperties, @PropertySource, @Profile, @Validated

Configuration & Profiles

Auto-configuration

@EnableAutoConfiguration, @Conditional, @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty, @AutoConfiguration

Core Concepts

AOP

@Aspect, @Around, @Before, @After

Core Concepts

Data access

@Transactional, @Query, @Id, @CreatedDate, @LastModifiedBy, @Indexed, @Node, @Relationship, @Document

Spring Data Overview and the per-store Spring Data pages

Caching

@EnableCaching, @Cacheable, @CachePut, @CacheEvict

Caching

Web / REST

@RequestMapping, @GetMapping/@PostMapping/etc., @PathVariable, @RequestBody, @Valid, @ExceptionHandler, @ControllerAdvice

REST APIs

gRPC

@GrpcService, @ImportGrpcClients

gRPC APIs

Messaging

@KafkaListener

Messaging with Kafka

Scheduling

@EnableScheduling, @Scheduled, @SchedulerLock

Scheduling & ShedLock

Observability

@Observed, @Timed, @Counted

Metrics & Observability

Mapping

@Mapper, @Mapping (MapStruct)

Lombok & MapStruct

Testing

@SpringBootTest, @WebMvcTest, @DataJpaTest, @DataMongoTest, @ExtendWith(MockitoExtension.class), @Mock, @InjectMocks

Unit & Integration Testing