Short Answer
Use these annotations according to the role of the class:
| Annotation | Use for | Typical layer |
|---|---|---|
@Component |
Generic Spring-managed class | Utility/infrastructure/helper |
@Service |
Business logic | Service layer |
@Repository |
Data access / persistence | Repository/DAO layer |
All three make the class a Spring bean, meaning Spring can create it, manage it, and inject it into other beans.
1. @Component: Generic Spring Bean
Use @Component when the class should be managed by Spring but does not clearly belong to the service, repository, or controller layer.
import org.springframework.stereotype.Component;
@Component
public class FileNameGenerator {
public String generate(String originalName) {
return System.currentTimeMillis() + "-" + originalName;
}
}
Good uses for @Component:
- formatters
- mappers
- validators
- helpers
- schedulers
- adapters
- general infrastructure classes
If a class contains business logic, prefer @Service instead.
2. @Service: Business Logic
Use @Service for classes that represent application/business operations.
import org.springframework.stereotype.Service;
@Service
public class EmployeeService {
private final EmployeeRepository employeeRepository;
public EmployeeService(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
public Employee getEmployee(Long id) {
return employeeRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Employee not found"));
}
}
Good uses for @Service:
- coordinating business workflows
- applying business rules
- calling repositories
- calling external APIs
- handling transactions
Example:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PayrollService {
private final EmployeeRepository employeeRepository;
public PayrollService(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@Transactional
public void processPayroll(Long employeeId) {
Employee employee = employeeRepository.findById(employeeId)
.orElseThrow(() -> new IllegalArgumentException("Employee not found"));
// business logic here
}
}
@Service is technically a specialized @Component, but it communicates intent:
this class contains business/service logic.
3. @Repository: Database/Data Access
Use @Repository for persistence classes: DAOs, database gateways, or repositories.
With Spring Data JPA, you usually define an interface:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
For Spring Data JPA interfaces, @Repository is often optional because Spring Data can detect repository interfaces automatically, but adding it is still common and makes the role explicit.
Use @Repository for:
- JPA repositories
- JDBC DAOs
- persistence adapters
- custom database access classes
Example custom DAO:
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeDao {
private final EntityManager entityManager;
public EmployeeDao(EntityManager entityManager) {
this.entityManager = entityManager;
}
public Employee findById(Long id) {
return entityManager.find(Employee.class, id);
}
}
@Repository also has an extra Spring meaning: it can participate in persistence exception translation, where database-specific exceptions are translated into Spring’s data access exception hierarchy.
4. Recommended Layering
A typical Spring MVC + Spring Data JPA flow looks like this:
Controller -> Service -> Repository -> Database
Example:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmployeeController {
private final EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@GetMapping("/employees/{id}")
public Employee getEmployee(@PathVariable Long id) {
return employeeService.getEmployee(id);
}
}
import org.springframework.stereotype.Service;
@Service
public class EmployeeService {
private final EmployeeRepository employeeRepository;
public EmployeeService(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
public Employee getEmployee(Long id) {
return employeeRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Employee not found"));
}
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
5. Prefer Constructor Injection
For all of these beans, prefer constructor injection:
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final PaymentClient paymentClient;
public OrderService(PaymentClient paymentClient) {
this.paymentClient = paymentClient;
}
public void placeOrder() {
paymentClient.charge();
}
}
Avoid field injection like this:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private PaymentClient paymentClient;
}
Field injection works, but constructor injection is usually better because:
- dependencies are explicit
- fields can be
final - the class is easier to test
- the object cannot be created without required dependencies
If you use Lombok, this is common:
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentClient paymentClient;
public void placeOrder() {
paymentClient.charge();
}
}
6. Common Mistakes
Mistake 1: Using @Component for everything
This works:
@Component
public class EmployeeService {
}
But this is clearer:
@Service
public class EmployeeService {
}
Use the most specific annotation when possible.
Mistake 2: Putting business logic in repositories
Avoid this:
@Repository
public class EmployeeRepository {
public void calculateBonusAndSaveEmployee() {
// business rules mixed with database access
}
}
Prefer:
Service: business rules
Repository: database access
Mistake 3: Injecting repositories directly into controllers
This is not always wrong, but for non-trivial applications it usually leads to poor layering.
Less ideal:
@RestController
public class EmployeeController {
private final EmployeeRepository employeeRepository;
public EmployeeController(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
}
Better:
@RestController
public class EmployeeController {
private final EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
}
The service layer gives you a place for validation, transactions, business rules, and orchestration.
7. Component Scanning Matters
Spring only finds these annotations if the classes are inside packages that Spring scans.
In Spring Boot, this usually works automatically if your main class is in the root package:
com.example.app
├── Application.java
├── controller
│ └── EmployeeController.java
├── service
│ └── EmployeeService.java
└── repository
└── EmployeeRepository.java
If your annotated classes are outside the scanned package, Spring will not create beans for them.
Rule of Thumb
Use this:
@Component = generic Spring-managed class
@Service = business logic
@Repository = data access
@Controller / @RestController = web layer
For most applications:
@RestController
public class EmployeeController {
private final EmployeeService employeeService;
}
@Service
public class EmployeeService {
private final EmployeeRepository employeeRepository;
}
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
That is the standard and correct way to use them.
