Foundation · Understand
Stereotype Annotations
@Component, @Service, @Repository, and @Controller all register beans; the differences are declared intent plus the machinery specific stereotypes switch on.
Primary source: Spring Framework 6.2 Reference: Classpath Scanning and Managed Components
Lesson 5 of 14
1d streakThe Architectural Question
Spraying @Component everywhere compiles fine, but you lose exception translation, layer intent in architecture tests, and any tooling that reads stereotypes.
What this guide proves
- @Component
- @Service
- @Repository
The meta-annotation hierarchy: @Component as the foundation
In the Spring Framework, annotations are not isolated keywords; they form an extensible, composable meta-annotation hierarchy. At the root of all Spring-managed components is @Component. Specialized stereotypes—such as @Service, @Repository, and @Controller—are meta-annotated with @Component, inheriting its component-scanning eligibility while adding specific semantic and infrastructure behavior.
| Stereotype | Meta-Annotated With | Architectural Role & Container Enhancements |
|---|---|---|
| @Component | None (Root stereotype) | General-purpose managed bean when no specialized architectural role fits. |
| @Service | @Component | Business logic layer. Serves as a semantic pointcut target for transaction boundaries and architectural linting (e.g., ArchUnit). |
| @Repository | @Component | Persistence gateway. Enables automatic persistence exception translation into Spring's DataAccessException hierarchy via PersistenceExceptionTranslationPostProcessor. |
| @Controller | @Component | Web presentation layer. Detected by DispatcherServlet to map HTTP request routes to handler methods. |
| @RestController | @Controller + @ResponseBody | REST API presentation layer. Combines @Controller with automatic response serialization (Jackson JSON) on every method. |
| @Configuration | @Component | Configuration class containing @Bean factory definitions. Can be CGLIB-proxied to enforce singleton semantics. |
Why not use @Component for everything?
Component scanning internals: ASM bytecode readers without class loading
A common misconception is that Spring uses standard Java reflection (Class.forName()) to inspect every file in a project during component scanning. In reality, loading hundreds of classes into JVM Metaspace simply to read annotations would severely degrade startup performance and trigger static initialization prematurely.
Instead, Spring's ClassPathScanningCandidateComponentProvider uses the ASM bytecode analysis library. It reads the raw .class bytecode streams directly from the filesystem or JARs, parsing annotation metadata without loading the class into the JVM ClassLoader until it is selected for bean registration.
@Configuration
@ComponentScan(
basePackages = "com.example.billing",
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = AuditedService.class
),
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = ".*Mock.*"
)
)
public class BillingModuleConfig {
// Defines explicit scan boundaries with ASM-level filtering
}The hidden power of @Repository: Persistence exception translation
Beyond documentation, @Repository triggers concrete runtime behavior: it enables automatic persistence exception translation. When a bean is annotated with @Repository, Spring registers a PersistenceExceptionTranslationAdvisor around it.
Architectural Boundary · Do Not Confuse
Raw Vendor Exceptions (SQLException, HibernateException)
Spring DataAccessException Hierarchy (CannotAcquireLockException, DuplicateKeyException)
Different databases and ORMs throw vendor-specific checked/unchecked exceptions with cryptic SQL error codes. @Repository translates these into Spring's portable, technology-neutral DataAccessException hierarchy, ensuring domain logic never couples to Hibernate or JDBC drivers.
Prerequisite for exception translation
Break it deliberately: The missed scan boundary trap
By default, @SpringBootApplication enables component scanning starting from the package in which the application class resides, searching all child packages. Placing a component in an external or sibling package leaves it completely undiscovered.
// File 1: com.example.app.Application (Scan root: com.example.app.*)
package com.example.app;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// File 2: com.example.external.PaymentService (OUTSIDE the scan root!)
package com.example.external;
@Service
public class PaymentService {} // Undiscovered!APPLICATION FAILED TO START *************************** Description: Field paymentService in com.example.app.CheckoutController required a bean of type 'com.example.external.PaymentService' that could not be found. Action: Consider defining a bean of type 'com.example.external.PaymentService' in your configuration, or relocate PaymentService into package com.example.app or a sub-package.
Diagnostic procedure
Check your understanding
Active Recall Checkpoint
How does Spring scan hundreds of classpath classes efficiently at boot without loading them into JVM Metaspace?
Hint: Consider the low-level bytecode library Spring embeds.
Show answer & explanation
Spring uses the ASM bytecode manipulation library to parse .class byte streams directly from disk or JAR archives, extracting annotation metadata without loading the classes into the JVM ClassLoader.
Active Recall Checkpoint
What unique runtime capability does @Repository provide that @Component and @Service do not?
Hint: Think about database error codes and exception wrapping.
Show answer & explanation
@Repository targets classes for automatic persistence exception translation via PersistenceExceptionTranslationPostProcessor, converting vendor-specific JDBC or Hibernate exceptions into Spring portable DataAccessException hierarchy.
Working Implementation
Code Walkthrough
Convert a generic @Component into a @Repository and observe the automatic persistence exception translation.
package com.example.core;
import org.springframework.dao.CannotAcquireLockException;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
public class StereotypeHierarchyLab {
// @Repository enables automatic PersistenceExceptionTranslationPostProcessor advice
@Repository
public static class TaskRepository {
public void save(long id, String title) {
// Simulating a database engine lock acquisition failure
throw new CannotAcquireLockException("Lock wait timeout exceeded on tasks table");
}
}
@Service
public static class TaskService {
private final TaskRepository repository;
public TaskService(TaskRepository repository) { this.repository = repository; }
public void completeTask(long id) {
try {
repository.save(id, "Completed");
} catch (DataAccessException ex) {
// Portable Spring exception hierarchy translates vendor-specific database codes
System.err.println("[Layered Recovery] Caught Spring DataAccessException: " + ex.getMessage());
}
}
}
}Synthesis & Production Verification
Production Acceptance Criteria
- Choose the stereotype that matches each layer
- Demonstrate exception translation switching on with @Repository
- Explain which stereotypes add behavior beyond bean detection
Key Takeaway
@Component is the root meta-annotation for all Spring stereotypes. Specialized stereotypes communicate architectural boundaries and activate specific infrastructure, such as @Repository enabling automatic persistence exception translation.
Architectural & Technical Review
Core Interview Questions
Q01How does Spring scan hundreds of classes at startup without exhausting Metaspace memory or loading classes prematurely?Show answer
Spring uses the ASM bytecode manipulation library via ClassPathScanningCandidateComponentProvider to inspect .class files directly on disk or inside JARs, parsing annotation metadata without loading classes into the JVM ClassLoader until selected.
Q02What concrete runtime functionality does @Repository provide compared to a generic @Component?Show answer
@Repository enables automatic persistence exception translation via PersistenceExceptionTranslationPostProcessor, catching vendor-specific JDBC or Hibernate exceptions and translating them into Spring's unified DataAccessException hierarchy.
Q03What causes a NoSuchBeanDefinitionException on a class clearly marked with @Service, and how is it resolved?Show answer
The class is located outside the base package of the @SpringBootApplication class. By default, component scanning only searches the declaring package and its subpackages. Moving the class into the application package tree or configuring @ComponentScan(basePackages = {...}) resolves it.
Production Pitfalls
Critical Anti-Patterns
- Pitfall: Placing components in sibling packages outside the scan root — By default, @SpringBootApplication scans only its package and subpackages. Sibling packages remain completely undiscovered.
- Pitfall: Assuming @Repository translates exceptions without infrastructure — @Repository requires a PersistenceExceptionTranslationPostProcessor bean in the context (configured automatically by Spring Data starters) to enable translation.
Primary Documentation & Specifications
Lesson Primary References
Supplementary Module Resources