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

15 min

Lesson 5 of 14

1d streak

The 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.

StereotypeMeta-Annotated WithArchitectural Role & Container Enhancements
@ComponentNone (Root stereotype)General-purpose managed bean when no specialized architectural role fits.
@Service@ComponentBusiness logic layer. Serves as a semantic pointcut target for transaction boundaries and architectural linting (e.g., ArchUnit).
@Repository@ComponentPersistence gateway. Enables automatic persistence exception translation into Spring's DataAccessException hierarchy via PersistenceExceptionTranslationPostProcessor.
@Controller@ComponentWeb presentation layer. Detected by DispatcherServlet to map HTTP request routes to handler methods.
@RestController@Controller + @ResponseBodyREST API presentation layer. Combines @Controller with automatic response serialization (Jackson JSON) on every method.
@Configuration@ComponentConfiguration class containing @Bean factory definitions. Can be CGLIB-proxied to enforce singleton semantics.

Why not use @Component for everything?

While the IoC container registers @Component, @Service, and @Repository identically in terms of singleton lifecycle, specialized stereotypes communicate architectural intent, allow targeted AOP pointcuts (e.g. logging all @Service methods), and activate critical infrastructure like @Repository exception translation.

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.

ScanBoundaryConfig.java
@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

Concept A

Raw Vendor Exceptions (SQLException, HibernateException)

Concept B

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

Automatic exception translation requires a PersistenceExceptionTranslationPostProcessor bean in the container. Spring Boot automatically configures this when spring-boot-starter-data-jpa or spring-boot-starter-jdbc is on the classpath.

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.

ApplicationStructure.java
// 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!
Terminal / Diagnostic Output
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

When NoSuchBeanDefinitionException occurs on a component that has @Service, do NOT add @Autowired or change qualifiers. First inspect the package declaration against the @SpringBootApplication package root.

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.

StereotypeHierarchyLab.java
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());
            }
        }
    }
}
@Component is the generic stereotype; @Service, @Repository, and @Controller declare architectural layer intent.
@Repository specifically registers PersistenceExceptionTranslationPostProcessor to translate JDBC/vendor errors into Spring DataAccessException.
Stereotypes establish clear architectural boundaries for automated architecture testing (such as ArchUnit).

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

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

Supplementary Module Resources