Skip to content

Repository / Service / Controller

A common starting point for Spring Boot apps. Each layer has one responsibility and only talks to the layer directly below it:

Controller  →  Service  →  Repository  →  Database

Repository

A repository is an interface for accessing data from a database. It extends JpaRepository, which gives you a set of CRUD methods for free:

Method Description
findAll() Returns all records
findById(id) Returns an Optional by primary key
save(entity) Inserts or updates a record
deleteById(id) Deletes a record by primary key
existsById(id) Returns true if the record exists

You can also define custom queries just by following JPA's naming convention — no SQL needed. JPA reads the method name and generates the query automatically:

Optional<User> findByFirstname(String firstname);
// → SELECT * FROM users WHERE firstname = ?

List<User> findByAgeGreaterThan(int age);
// → SELECT * FROM users WHERE age > ?

@Repository tells Spring Boot to manage this bean, though it's technically optional since Spring detects any interface extending JpaRepository. It's still good practice to keep it.

The broader principle behind depending on an interface rather than a concrete class — and why it matters — is covered in SOLID — Dependency Inversion.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByFirstname(String firstname);
}

Service

A service holds the business logic. Controllers call it, and it calls the repository. This is where you add validations, transformations, or any processing before reading/writing data.

The repository is injected via constructor injection (preferred over @Autowired on a field) because:

  • Dependencies are explicit and required — no hidden state
  • Fields can be final, making the class immutable
  • Easier to unit test — you can pass a mock directly through the constructor
@Service
public class UserService {
    private final UserRepository userRepository;

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

    public User updateUser(User modifiedUser) {
        return userRepository.findById(modifiedUser.getId())
            .map(existingUser -> {
                existingUser.setFirstname(modifiedUser.getFirstname());
                // ...
                return userRepository.save(existingUser);
            })
            .orElseThrow(() -> new RuntimeException("User not found"));
    }
}

Controller

A controller has no business logic — it only receives HTTP requests and delegates to the right service method.

  • @RestController is a shortcut for @Controller + @ResponseBody, meaning every method returns JSON directly.
  • @RequestMapping("/api/users") sets the base path for all endpoints in the class.
  • HTTP methods are mapped with @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, etc.
  • ResponseEntity<T> lets you control the full HTTP response: status code, headers, and body.

Spring Boot automatically handles constructor injection when there is only one constructor, so no @Autowired is needed.

@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserService userService;

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

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return userService.getUserById(id)
            .map(ResponseEntity::ok)
            .orElseGet(() -> ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        return new ResponseEntity<>(userService.createUser(user), HttpStatus.CREATED);
    }
}

@PathVariable binds {id} from the URL to the method parameter. @RequestBody deserializes the JSON request body into a Java object.


This pattern works well for straightforward CRUD apps. As services grow and business logic gets more complex, consider organizing around use cases instead — see Clean Architecture.