How do I organize service, repository and controller layers in Spring?

Typical Spring Layer Organization

A clean Spring application usually separates code into controller, service, repository, and model/entity layers.

com.example.app
├── AppApplication.java
├── controller
│   └── UserController.java
├── service
│   └── UserService.java
├── repository
│   └── UserRepository.java
├── entity
│   └── User.java
└── dto
    ├── CreateUserRequest.java
    └── UserResponse.java

The usual request flow is:

HTTP Request
    ↓
Controller
    ↓
Service
    ↓
Repository
    ↓
Database

1. Controller Layer

The controller handles HTTP requests and responses.

Use:

  • @RestController for JSON APIs
  • @Controller for server-rendered pages such as Thymeleaf/JSP

Controllers should be thin. They should mainly:

  • Accept requests
  • Validate input
  • Call services
  • Return responses

Example:

package com.example.app.controller;

import com.example.app.dto.CreateUserRequest;
import com.example.app.dto.UserResponse;
import com.example.app.service.UserService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public List<UserResponse> findAll() {
        return userService.findAll();
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public UserResponse create(@Valid @RequestBody CreateUserRequest request) {
        return userService.create(request);
    }
}

2. Service Layer

The service contains business logic.

Use @Service.

Services should:

  • Implement business rules
  • Coordinate multiple repositories
  • Handle transactions
  • Convert between entities and DTOs if your app is small or medium-sized

Example:

package com.example.app.service;

import com.example.app.dto.CreateUserRequest;
import com.example.app.dto.UserResponse;
import com.example.app.entity.User;
import com.example.app.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional(readOnly = true)
    public List<UserResponse> findAll() {
        return userRepository.findAll()
                .stream()
                .map(user -> new UserResponse(
                        user.getId(),
                        user.getName(),
                        user.getEmail()
                ))
                .toList();
    }

    @Transactional
    public UserResponse create(CreateUserRequest request) {
        User user = new User();
        user.setName(request.name());
        user.setEmail(request.email());

        User savedUser = userRepository.save(user);

        return new UserResponse(
                savedUser.getId(),
                savedUser.getName(),
                savedUser.getEmail()
        );
    }
}

Use @Transactional on service methods rather than controller methods.


3. Repository Layer

The repository handles database access.

With Spring Data JPA, you usually define an interface that extends JpaRepository.

package com.example.app.repository;

import com.example.app.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

Spring Data JPA automatically provides common methods such as:

findAll()
findById(id)
save(entity)
deleteById(id)

You can also add query methods:

package com.example.app.repository;

import com.example.app.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findByEmail(String email);

    boolean existsByEmail(String email);
}

You generally do not need to annotate Spring Data repository interfaces with @Repository; Spring detects them automatically.


4. Entity Layer

The entity represents database tables.

Use Jakarta persistence imports:

package com.example.app.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;
}

Entities should mostly represent persistent state. Avoid putting HTTP-specific logic in entities.


5. DTO Layer

DTOs separate your API contract from your database model.

Request DTO:

package com.example.app.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record CreateUserRequest(
        @NotBlank String name,
        @Email @NotBlank String email
) {
}

Response DTO:

package com.example.app.dto;

public record UserResponse(
        Long id,
        String name,
        String email
) {
}

Using DTOs helps avoid exposing internal entity fields directly through your API.


Recommended Responsibilities

Layer Annotation Responsibility
Controller @RestController, @Controller HTTP request/response handling
Service @Service Business logic, transactions
Repository Spring Data JpaRepository Database access
Entity @Entity Database table mapping
DTO/Form Records/classes with validation API input/output models

Dependency Direction

Keep dependencies flowing one way:

Controller → Service → Repository → Entity

Avoid this:

Repository → Service
Service → Controller
Entity → Controller

For example:

  • A controller can inject a service.
  • A service can inject a repository.
  • A repository should not know about services or controllers.
  • Entities should not depend on web/controller classes.

Best Practices

  1. Use constructor injection
    @Service
    public class OrderService {
    
        private final OrderRepository orderRepository;
    
        public OrderService(OrderRepository orderRepository) {
            this.orderRepository = orderRepository;
        }
    }
    
  2. Keep controllers thin

    Bad:

    @PostMapping
    public User create(@RequestBody User user) {
        if (user.getEmail() == null) {
            throw new IllegalArgumentException("Email is required");
        }
    
        return userRepository.save(user);
    }
    

    Better:

    @PostMapping
    public UserResponse create(@Valid @RequestBody CreateUserRequest request) {
        return userService.create(request);
    }
    
  3. Put transactions in services
    @Transactional
    public UserResponse create(CreateUserRequest request) {
        // business logic and repository calls
    }
    
  4. Use DTOs for API boundaries

    Do not expose entities directly unless the application is very small or internal.

  5. Keep the main application class in the root package

    com.example.app.AppApplication
    

That way Spring can scan:

com.example.app.controller
com.example.app.service
com.example.app.repository
com.example.app.entity

Feature-Based Alternative

For larger applications, you may prefer organizing by feature instead of technical layer:

com.example.app
├── user
│   ├── UserController.java
│   ├── UserService.java
│   ├── UserRepository.java
│   ├── User.java
│   ├── CreateUserRequest.java
│   └── UserResponse.java
├── order
│   ├── OrderController.java
│   ├── OrderService.java
│   ├── OrderRepository.java
│   └── Order.java
└── AppApplication.java

This is often easier to maintain as the project grows because related files stay together.


Simple Rule of Thumb

Ask this when deciding where code belongs:

  • Is it about HTTP? Put it in the controller.
  • Is it business logic? Put it in the service.
  • Is it database access? Put it in the repository.
  • Is it database structure? Put it in the entity.
  • Is it request/response shape? Put it in a DTO.

For most Spring applications, the clean structure is:

Controller → Service → Repository → Database

with DTOs at the API boundary and entities at the persistence boundary.

How do I use @Component, @Service, and @Repository correctly?

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.

Understanding @Entity, @Repository, and @Service in Spring Boot

In Spring Boot (and the larger Spring Framework), the annotations @Entity, @Repository, and @Service play a key role in structuring and organizing applications using the principles of dependency injection and inversion of control. Here’s an overview of each:


1. @Entity

  • Definition: The @Entity annotation is used in Java Persistence API (JPA) to define a class as a persistent entity. This means the class maps to a table in the database.
  • Key Features:
    • Marks a POJO (Plain Old Java Object) as a JPA entity.
    • Each annotated class is associated with a database table, and each instance of the class represents a row in that table.
    • Requires a primary key, typically annotated with @Id.
  • Example:

package org.kodejava.spring;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Employee {
    @Id
    private Long id;
    private String name;
    private String role;

    // Getters and setters
}
  • Usage Context: This annotation is part of Jakarta EE (or JPA) and is generally used for classes that model database tables.

2. @Repository

  • Definition: The @Repository annotation indicates that the class is a repository, which is responsible for interacting with the database.
  • Key Features:

    • Used for Data Access Objects (DAO).
    • It helps encapsulate the interaction with the database from the rest of the application.
    • It automatically translates exceptions thrown by the persistence layer into Spring’s unchecked exceptions (like DataAccessException).
  • Example:

package org.kodejava.spring;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    // Custom database queries (if needed)
}
  • Usage Context: Typically, @Repository is used to annotate interfaces or classes that handle data persistence, often enhanced by Spring Data JPA for reducing boilerplate code.

3. @Service

  • Definition: The @Service annotation marks a class as a business service that contains the application’s business logic.
  • Key Features:

    • Indicates that the class is a “service” component in the Service layer.
    • Helps clearly separate business logic from other concerns, such as data persistence or presentation.
    • Works in conjunction with @Component to allow dependency injection.
  • Example:

package org.kodejava.spring;

import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class EmployeeService {
    private final EmployeeRepository repository;

    // Constructor injection of the repository
    public EmployeeService(EmployeeRepository repository) {
        this.repository = repository;
    }

    public List<Employee> getAllEmployees() {
        return repository.findAll();
    }

    public Employee saveEmployee(Employee employee) {
        return repository.save(employee);
    }
}
  • Usage Context: Typically used to encapsulate and reuse business logic.

Summary of Their Responsibilities in an Application Layer:

  • @Entity: Maps a Java class to a database table (used in the Data Model layer).
  • @Repository: Handles database operations (typically at the Data Access layer).
  • @Service: Contains business logic (used in the Service layer).

How These Work Together:

These annotations correspond to different tiers in a common layering structure of a Spring Boot application:
1. Entity: Represents data (e.g., Employee).
2. Repository: Provides the CRUD operations for entities using JPA (e.g., EmployeeRepository).
3. Service: Manages the application’s business logic and interactions (e.g., EmployeeService).

By using these annotations together, you achieve a clean separation of concerns, making the application easier to maintain, test, and scale.