Here’s the English translation of the article body:


@Service for Service layer classes, @Repository for repositories. You’ve been adding them out of team habit, but when someone asks “How is that different from @Component?”, you find yourself stuck for an answer — sound familiar?

In this article, we’ll verify with actual working code the exact relationship between the three stereotype annotations and @Component, the unique effects that only @Repository and @Controller have, and the criteria for choosing between them for each layer.

Note that the basics of Bean registration with @Component and component scanning are covered in What is @Component?, so this article focuses on the “differences between” the three annotations.

The Conclusion First: This Table Is All You Need to Remember

AnnotationBean RegistrationUnique Effect
@ComponentYesNone (the base form)
@ServiceYesNone (semantic meaning only)
@RepositoryYesTranslates exceptions into DataAccessException
@ControllerYesBecomes a target for Spring MVC handler detection

The rule for choosing: “@Controller for the web layer (@RestController for REST APIs), @Service for the business logic layer, @Repository for the data access layer.” That’s all there is to it.

In terms of Bean registration, all four are completely identical, but @Repository and @Controller have additional mechanisms. Let’s look at them one by one.

All Three Are Derivatives of @Component

“Almost the same” isn’t a matter of intuition — it’s a fact written in the Spring Framework source code. Let’s look at the declaration of @Service.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component  // ← @Component自体がメタアノテーションとして付いている
public @interface Service {

	@AliasFor(annotation = Component.class)
	String value() default "";
}

You can see that @Component is attached to the definition of @Service. @Repository and @Controller have exactly the same structure.

Component scanning detects not only “classes annotated with @Component” but also “classes annotated with an annotation that is itself annotated with @Component.” This mechanism is called a meta-annotation.

In other words, when it comes to being registered as a Bean and becoming a target for DI, there is absolutely no difference between the four annotations. So why are three separate variants provided at all?

The Real Effect of @Repository Is Exception Translation

Of the three, @Repository has the most clear-cut unique feature.

Spring has a mechanism called PersistenceExceptionTranslationPostProcessor, which wraps Beans annotated with @Repository in a proxy and translates implementation-specific exceptions thrown by JPA or JDBC into Spring’s common DataAccessException hierarchy. In Spring Boot, this is auto-configured, so it’s enabled with no setup required.

Let’s verify this in practice. We’ll create a custom repository that uses EntityManager directly.

@Repository
public class ProductDao {

    @PersistenceContext
    private EntityManager em;

    public void save(Product product) {
        em.persist(product);
        em.flush(); // 制約違反をこの時点で発生させる
    }
}

With a unique constraint on the code column, let’s try saving a product with the same code twice.

@SpringBootTest
@Transactional
class ProductDaoTest {

    @Autowired
    ProductDao productDao;

    @Test
    void 一意制約違反はDataAccessExceptionに変換される() {
        productDao.save(new Product("SKU-001"));

        // JPAのPersistenceExceptionではなく、Springの例外がスローされる
        assertThrows(DataIntegrityViolationException.class,
                () -> productDao.save(new Product("SKU-001")));
    }
}

What JPA originally throws is a PersistenceException-family exception, but what you can actually catch is Spring’s DataIntegrityViolationException (a subclass of DataAccessException).

Why is this useful? Because the calling Service layer can write exception handling without caring whether the implementation uses JPA or JdbcTemplate. Even if you swap out the persistence technology, you don’t need to rewrite your catch clauses. For a full picture of exception handling in REST APIs, see the exception handling article as well.

And here’s the important part: if you change this class to @Component, exception translation stops working, and raw PersistenceExceptions start flying out. A clear functional difference.

Not Needed for Spring Data JPA Repository Interfaces

Here’s a common misconception.

// @Repositoryを付ける必要はない
public interface ProductRepository extends JpaRepository<Product, Long> {
}

Interfaces extending JpaRepository are registered as Beans by Spring Data JPA through its own mechanism, and exception translation is enabled by default too. Adding @Repository does no harm, but it serves no purpose either.

Remember this: the cases where you should add @Repository yourself are “hand-written” repository implementation classes that use EntityManager or JdbcTemplate directly, like the one shown earlier.

The Real Effect of @Controller Is Handler Detection

@Controller also has a unique effect. Spring MVC’s RequestMappingHandlerMapping looks for handler methods such as @GetMapping only inside Beans annotated with @Controller.

This means that if you change @Controller to @Component, the class is still registered as a Bean, but methods annotated with @GetMapping are not detected as handlers, and the endpoints return 404. This is counterexample number one to “wouldn’t everything work with just @Component?”

By the way, here’s what @RestController — familiar from REST APIs — actually is:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

	@AliasFor(annotation = Controller.class)
	String value() default "";
}

It’s a composite annotation of @Controller and @ResponseBody. Choose @Controller if you return views (view names), and @RestController for REST APIs that return JSON. The full flow of implementing a REST API is explained in detail in the CRUD API tutorial.

@Service Has No Unique Feature — Yet There Are 3 Reasons to Use It

Now for the main topic: @Service. In the current Spring Framework, @Service is actually a pure alias for @Component, with no additional processing whatsoever.

“Then why not just use @Component?” you might think. Even so, there are three reasons you should use @Service.

1. The Layer Is Communicated at a Glance

Just by looking at the annotation, you can tell “this class belongs to the business logic layer.” It also guides package structure and code reviews, which quietly pays off in team development. Our thinking on layer structure is summarized in the package structure article.

2. It Can Be Targeted by AOP Pointcuts

With @Service in place, you can apply cross-cutting concerns like logging and monitoring to the business logic layer only.

@Aspect
@Component
public class ServiceLogAspect {

    @Around("@within(org.springframework.stereotype.Service)")
    public Object log(ProceedingJoinPoint jp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return jp.proceed();
        } finally {
            System.out.println(jp.getSignature() + " " + (System.currentTimeMillis() - start) + "ms");
        }
    }
}

If you make everything @Component, you lose the ability to write this kind of “layer-targeted” cross-cutting logic.

3. It Prepares You for Future Feature Additions

The Javadoc for @Service states, in essence, that this specialization may become a target for additional functionality in future releases. In fact, exception translation was added to @Repository later on. Using annotations according to their meaning is itself a form of insurance.

Would Everything Still Work with Just @Component?

Summarizing everything so far, we can answer it like this.

There are cases where it works. Looking purely at DI resolution, replacing @Service or @Repository with @Component still lets the app start, and injection still succeeds. For the basics of DI, see the What is DI? article.

But there are two cases where things break:

  • Changing @Controller → @Component: handler mapping stops working and endpoints return 404
  • Changing @Repository → @Component: exception translation stops working, and errors slip past handling that assumes DataAccessException

In other words, the right approach is not to choose based on “whether it works,” but on “whether the layer’s intent is communicated and the unique features take effect correctly.”

Quick Reference Table

When writing a new class, just follow this table and you’ll never hesitate.

LayerAnnotationWhat You Get
Web layer (views)@ControllerHandler detection
Web layer (REST API)@RestControllerHandler detection + @ResponseBody
Business logic layer@ServiceLayer declaration, AOP targeting
Data access layer (custom implementation)@RepositoryException translation
Components that belong to no specific layer@ComponentBean registration only

The decision flow when in doubt is simple. If it receives HTTP requests, use the @Controller family; if it touches the database, use @Repository; if it’s business logic, use @Service; if it’s none of those, use @Component. And if the class handles configuration, that’s where @Configuration comes in.

Finally, let’s look at the finished form with all three layers side by side.

@RestController
public class ProductController {
    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping("/products/{id}")
    public Product get(@PathVariable Long id) {
        return productService.find(id);
    }
}

@Service
public class ProductService {
    private final ProductDao productDao;

    public ProductService(ProductDao productDao) {
        this.productDao = productDao;
    }

    public Product find(Long id) {
        return productDao.findById(id);
    }
}

@Repository
public class ProductDao {
    // EntityManagerやJdbcTemplateを使った実装
}

Constructor injection is the recommended way to inject dependencies. The reasons are explained in the injection style comparison article.

Summary

@Service, @Repository, and @Controller are all identical to @Component in terms of Bean registration. However, @Repository has the unique effect of exception translation and @Controller has handler detection, while @Service — despite having no functionality of its own — is worth using as a layer declaration and as a unit for applying AOP.

Using each annotation straightforwardly according to its layer is the shortest path to both readable code and full use of the framework’s features.

If you want to review how Bean registration itself works, see the difference between @Component and @Bean; if you want to start from the concept of DI, check out What is DI? as well.


Notes on the translation: all code blocks are unchanged (including Japanese comments and the Japanese test method name, per the “keep code examples unchanged” rule), and all internal links still point to the original /ja/ URLs as instructed. If this is for the English version of the site, you may want those links switched to /en/ equivalents — just say the word and I’ll update them.