How do I connect Spring to a database?

To connect Spring to a database, the usual modern approach is:

  1. Add database-related dependencies.
  2. Configure the database connection properties.
  3. Create an entity.
  4. Create a repository.
  5. Use the repository from a service or controller.

The simplest way is with Spring Boot + Spring Data JPA.


1. Add Maven Dependencies

For a Spring Boot application using JPA, add:

<dependencies>
    <!-- Spring Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- Example: PostgreSQL driver -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

For MySQL instead:

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

If you are also building REST endpoints, you usually include:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. Configure application.properties

Create or update:

src/main/resources/application.properties

Example for PostgreSQL:

spring.datasource.url=jdbc:postgresql://localhost:5432/app
spring.datasource.username=postgres
spring.datasource.password=postgres

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

Example for MySQL:

spring.datasource.url=jdbc:mysql://localhost:3306/app
spring.datasource.username=root
spring.datasource.password=secret

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

Spring Boot will automatically create a DataSource, configure Hibernate, and connect Spring Data JPA to the database.


3. Create an Entity

Example:

package com.example.app.user;

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;
}

The @Entity annotation tells JPA that this class maps to a database table.


4. Create a Repository

package com.example.app.user;

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

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

This gives you built-in methods such as:

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

You do not need to manually open JDBC connections for common CRUD operations.


5. Use the Repository in a Service

package com.example.app.user;

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<User> findAll() {
        return userRepository.findAll();
    }

    @Transactional
    public User save(User user) {
        return userRepository.save(user);
    }
}

Use @Transactional for methods that interact with the database.


6. Optional REST Controller Example

package com.example.app.user;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    private final UserService userService;

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

    @GetMapping("/users")
    public List<User> users() {
        return userService.findAll();
    }
}

When you visit:

http://localhost:8080/users

Spring will query the database through the repository and return the users as JSON.


7. Recommended ddl-auto Values

The property:

spring.jpa.hibernate.ddl-auto=update

controls how Hibernate manages tables.

Common values:

Value Meaning
none Do not change the schema
validate Check that tables match entities
update Update tables automatically
create Drop and recreate tables on startup
create-drop Create on startup, drop on shutdown

For learning, update is convenient.

For production, prefer:

spring.jpa.hibernate.ddl-auto=validate

and use a migration tool such as Flyway or Liquibase.


8. Typical Spring Database Flow

Controller
    ↓
Service
    ↓
Repository
    ↓
Spring Data JPA / Hibernate
    ↓
DataSource
    ↓
Database

Quick Checklist

To connect Spring to a database:

  1. Add spring-boot-starter-data-jpa.
  2. Add the database driver, such as PostgreSQL or MySQL.
  3. Configure spring.datasource.url, username, and password.
  4. Create an @Entity.
  5. Create a JpaRepository.
  6. Inject the repository into a service.
  7. Use @Transactional for database operations.

For most Spring applications, you should let Spring Boot configure the DataSource automatically instead of manually creating JDBC connections.

How do I handle exceptions globally in Spring MVC?

In Spring MVC, handle exceptions globally by creating a class annotated with @ControllerAdvice or @RestControllerAdvice and adding methods annotated with @ExceptionHandler.

For REST APIs, prefer @RestControllerAdvice, because it combines @ControllerAdvice and @ResponseBody, so returned objects are serialized as JSON automatically.

package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
            ResourceNotFoundException ex
    ) {
        ErrorResponse error = new ErrorResponse(
                HttpStatus.NOT_FOUND.value(),
                ex.getMessage()
        );

        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGenericException(
            Exception ex
    ) {
        ErrorResponse error = new ErrorResponse(
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                "An unexpected error occurred"
        );

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

Example error response DTO:

package com.example.demo.exception;

import java.time.Instant;

public record ErrorResponse(
        int status,
        String message,
        Instant timestamp
) {
    public ErrorResponse(int status, String message) {
        this(status, message, Instant.now());
    }
}

Example custom exception:

package com.example.demo.exception;

public class ResourceNotFoundException extends RuntimeException {

    public ResourceNotFoundException(String message) {
        super(message);
    }
}

Then you can throw exceptions from controllers or services:

throw new ResourceNotFoundException("User not found");

Spring will automatically route that exception to the matching @ExceptionHandler.

Common handlers you may want to add:

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
        MethodArgumentNotValidException ex
) {
    ErrorResponse error = new ErrorResponse(
            HttpStatus.BAD_REQUEST.value(),
            "Validation failed"
    );

    return ResponseEntity.badRequest().body(error);
}

Use:

  • @RestControllerAdvice for REST APIs returning JSON.
  • @ControllerAdvice for MVC apps returning views or when you manually use ResponseEntity.
  • Specific exception handlers before generic ones.
  • A final @ExceptionHandler(Exception.class) as a fallback.

How do I validate form data in Spring?

In Spring MVC, the standard way to validate form data is to use Jakarta Bean Validation annotations on a form/DTO object, then check validation results in your controller with BindingResult.

Since your project uses Jakarta EE, use jakarta.validation.* imports.

1. Add validation annotations to your form object

Example form/DTO:

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

public class UserForm {

    @NotBlank(message = "Name is required")
    @Size(max = 100, message = "Name must be at most 100 characters")
    private String name;

    @NotBlank(message = "Email is required")
    @Email(message = "Please enter a valid email address")
    private String email;

    @NotBlank(message = "Password is required")
    @Size(min = 8, message = "Password must be at least 8 characters")
    private String password;

    // getters and setters
}

With Lombok:

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserForm {

    @NotBlank(message = "Name is required")
    @Size(max = 100, message = "Name must be at most 100 characters")
    private String name;

    @NotBlank(message = "Email is required")
    @Email(message = "Please enter a valid email address")
    private String email;

    @NotBlank(message = "Password is required")
    @Size(min = 8, message = "Password must be at least 8 characters")
    private String password;
}

2. Use @Valid in your controller

import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class UserController {

    @GetMapping("/register")
    public String showRegisterForm(Model model) {
        model.addAttribute("userForm", new UserForm());
        return "register";
    }

    @PostMapping("/register")
    public String register(
            @Valid UserForm userForm,
            BindingResult bindingResult
    ) {
        if (bindingResult.hasErrors()) {
            return "register";
        }

        // Save user or call service layer here
        return "redirect:/register/success";
    }
}

Important: BindingResult must come immediately after the validated object.

Correct:

public String register(@Valid UserForm userForm, BindingResult bindingResult)

Incorrect:

public String register(@Valid UserForm userForm, Model model, BindingResult bindingResult)

3. Display errors in Thymeleaf

If you use Thymeleaf:

<form th:action="@{/register}" th:object="${userForm}" method="post">
    <div>
        <label>Name</label>
        <input type="text" th:field="*{name}">
        <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
    </div>

    <div>
        <label>Email</label>
        <input type="email" th:field="*{email}">
        <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span>
    </div>

    <div>
        <label>Password</label>
        <input type="password" th:field="*{password}">
        <span th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></span>
    </div>

    <button type="submit">Register</button>
</form>

4. Common validation annotations

@NotNull
@NotBlank
@NotEmpty
@Size(min = 2, max = 100)
@Min(18)
@Max(120)
@Email
@Pattern(regexp = "...")
@Past
@Future
@Positive
@PositiveOrZero

Use:

  • @NotNull for any value that must not be null
  • @NotBlank for strings that must contain non-whitespace text
  • @NotEmpty for strings, collections, arrays, or maps that must not be empty
  • @Size for string length or collection size
  • @Email for email format validation

5. Maven dependency

If you use Spring Boot, add:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

For Gradle:

implementation("org.springframework.boot:spring-boot-starter-validation")

6. Service-layer validation

You can also validate method parameters in Spring services:

import jakarta.validation.Valid;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Service
@Validated
public class UserService {

    public void createUser(@Valid UserForm userForm) {
        // business logic
    }
}

7. REST API validation example

For JSON request bodies:

import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserRestController {

    @PostMapping("/api/users")
    public String createUser(@Valid @RequestBody UserForm userForm) {
        return "User created";
    }
}

For REST APIs, invalid input usually results in a 400 Bad Request.

Summary

Use this pattern:

@PostMapping("/submit")
public String submit(@Valid MyForm form, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return "form-page";
    }

    return "redirect:/success";
}

That is the typical Spring MVC form validation flow.

How do I handle HTTP requests with Spring controllers?

In Spring MVC or Spring Boot, you handle HTTP requests by creating controller classes. A controller receives a request, runs application logic, and returns either:

  • a view name for server-rendered pages, or
  • data such as JSON for REST APIs.

1. Basic Spring MVC Controller

Use @Controller when you want to return views such as JSP, Thymeleaf, or other templates.

package com.example.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("message", "Welcome to Spring MVC!");
        return "home";
    }
}

In this example:

  • @Controller marks the class as a Spring MVC controller.
  • @GetMapping("/") handles HTTP GET /.
  • Model passes data to the view.
  • "home" is the logical view name.

If you use Thymeleaf, Spring would typically look for:

src/main/resources/templates/home.html

2. REST Controller Returning JSON

Use @RestController when you want to build REST APIs.

package com.example.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingRestController {

    @GetMapping("/api/greeting")
    public Greeting greeting() {
        return new Greeting("Hello from Spring!");
    }

    public record Greeting(String message) {
    }
}

Calling:

GET /api/greeting

returns JSON like:

{
  "message": "Hello from Spring!"
}

@RestController is a shortcut for:

@Controller
@ResponseBody

So every method returns the response body directly instead of a view name.


3. Handling Different HTTP Methods

Spring provides convenient annotations for common HTTP methods.

package com.example.web;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @GetMapping("/users")
    public String getUsers() {
        return "Get all users";
    }

    @PostMapping("/users")
    public String createUser() {
        return "Create a new user";
    }

    @PutMapping("/users/1")
    public String replaceUser() {
        return "Replace user";
    }

    @PatchMapping("/users/1")
    public String updateUser() {
        return "Update part of user";
    }

    @DeleteMapping("/users/1")
    public String deleteUser() {
        return "Delete user";
    }
}

Common mappings include:

Annotation HTTP Method
@GetMapping GET
@PostMapping POST
@PutMapping PUT
@PatchMapping PATCH
@DeleteMapping DELETE

4. Reading Path Variables

Use @PathVariable to read values from the URL path.

package com.example.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    @GetMapping("/products/{id}")
    public String getProduct(@PathVariable Long id) {
        return "Product ID: " + id;
    }
}

Request:

GET /products/10

Response:

Product ID: 10

5. Reading Query Parameters

Use @RequestParam to read query string parameters.

package com.example.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SearchController {

    @GetMapping("/search")
    public String search(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "1") int page
    ) {
        return "Searching for: " + keyword + ", page: " + page;
    }
}

Request:

GET /search?keyword=spring&page=2

Response:

Searching for: spring, page: 2

6. Reading Request Body JSON

Use @RequestBody to bind JSON request data to a Java object.

package com.example.web;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

public record CreateUserRequest(String name, String email) {
}

@RestController
public class UserApiController {

    @PostMapping("/api/users")
    public String createUser(@RequestBody CreateUserRequest request) {
        return "Created user: " + request.name() + " with email: " + request.email();
    }
}

Request:

POST /api/users
Content-Type: application/json
{
  "name": "Alice",
  "email": "[email protected]"
}

7. Returning Proper HTTP Status Codes

Use ResponseEntity when you need control over the response status, headers, or body.

package com.example.web;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {

    @PostMapping("/orders")
    public ResponseEntity<String> createOrder() {
        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body("Order created");
    }
}

This returns:

HTTP/1.1 201 Created

8. Handling Form Submissions

For traditional web applications, a controller can handle form submissions with @PostMapping.

package com.example.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class ContactController {

    @PostMapping("/contact")
    public String submitContactForm(
            @RequestParam String name,
            @RequestParam String message
    ) {
        System.out.println("Name: " + name);
        System.out.println("Message: " + message);

        return "redirect:/contact-success";
    }
}

The redirect prevents duplicate form submissions if the user refreshes the page.


9. Using a Service from a Controller

Controllers should usually be thin. Put business logic in a service class.

package com.example.web;

import com.example.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    private final UserService userService;

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

    @GetMapping("/api/users/count")
    public int countUsers() {
        return userService.countUsers();
    }
}
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public int countUsers() {
        return 5;
    }
}

This keeps the controller focused on HTTP request/response handling.


10. Simple Exception Handling

You can handle exceptions globally using @ControllerAdvice.

package com.example.web;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ErrorResponse(ex.getMessage());
    }

    public record ErrorResponse(String message) {
    }
}

Now if a controller throws:

throw new IllegalArgumentException("Invalid request");

Spring returns a 400 Bad Request response.


Quick Summary

Use these annotations to handle HTTP requests in Spring:

Annotation Purpose
@Controller Web controller returning views
@RestController REST controller returning response bodies
@GetMapping Handle GET requests
@PostMapping Handle POST requests
@PutMapping Handle PUT requests
@PatchMapping Handle PATCH requests
@DeleteMapping Handle DELETE requests
@PathVariable Read values from the URL path
@RequestParam Read query parameters or form fields
@RequestBody Read JSON/XML request body
ResponseEntity Customize status, headers, and body
@ControllerAdvice Centralized exception handling

In short:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, Spring!";
    }
}

That is the simplest form of handling an HTTP request with a Spring controller.

How do I build a web application using Spring MVC?

Building a Web Application Using Spring MVC

A Spring MVC web application is typically built around these pieces:

  1. DispatcherServlet — the front controller that receives HTTP requests.
  2. Spring MVC configuration — enables MVC and configures controllers, view resolution, static resources, etc.
  3. Controllers — handle web requests.
  4. Services — contain business logic.
  5. Repositories — handle persistence, often with Spring Data JPA.
  6. Views or REST responses — return HTML pages, JSON, text, etc.
  7. Deployment setup — either Spring Boot embedded server or traditional WAR deployment.

1. Choose an Application Style

There are two common ways to build Spring MVC applications.

Option A: Spring Boot MVC Application

This is the most common modern approach.

You create an executable application with an embedded server such as Tomcat.

Option B: Traditional Spring MVC WAR Application

You deploy a WAR file to an external servlet container such as Tomcat.

Both use Spring MVC, but Spring Boot reduces configuration significantly.


Option A: Spring Boot + Spring MVC

2. Add Dependencies

If using Maven, a basic Spring MVC web application can start with:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Optional: for validation -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <!-- Optional: for JPA/database access -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
</dependencies>

spring-boot-starter-web includes Spring MVC and an embedded servlet container.


3. Create the Main Application Class

package com.example.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class, args);
    }
}

@SpringBootApplication enables component scanning, auto-configuration, and Spring configuration support.

Recommended package structure:

com.example.app
├── WebApplication.java
├── controller
│   └── HomeController.java
├── service
│   └── GreetingService.java
├── repository
│   └── UserRepository.java
└── model
    └── User.java

Keep the main class in the root package so Spring can scan subpackages.


4. Create a REST Controller

For JSON/text responses, use @RestController.

package com.example.app.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloRestController {

    @GetMapping("/api/hello")
    public String hello() {
        return "Hello from Spring MVC";
    }
}

Run the application and visit:

http://localhost:8080/api/hello

5. Create an MVC Controller That Returns a View

If you want server-rendered HTML pages, use @Controller.

package com.example.app.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("message", "Welcome to Spring MVC");
        return "home";
    }
}

With Thymeleaf, add:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Then create:

src/main/resources/templates/home.html
<!DOCTYPE html>
<html>
<head>
    <title>Spring MVC</title>
</head>
<body>
    <h1 th:text="${message}">Default message</h1>
</body>
</html>

Spring Boot automatically configures Thymeleaf templates from src/main/resources/templates.


6. Add a Service Layer

Controllers should usually delegate business logic to services.

package com.example.app.service;

import org.springframework.stereotype.Service;

@Service
public class GreetingService {

    public String getGreeting() {
        return "Hello from the service layer";
    }
}

Inject the service into a controller using constructor injection:

package com.example.app.controller;

import com.example.app.service.GreetingService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

    private final GreetingService greetingService;

    public GreetingController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/api/greeting")
    public String greeting() {
        return greetingService.getGreeting();
    }
}

7. Handle Form Data

For a traditional web form:

package com.example.app.controller;

import com.example.app.form.ContactForm;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class ContactController {

    @GetMapping("/contact")
    public String showForm(Model model) {
        model.addAttribute("contactForm", new ContactForm());
        return "contact";
    }

    @PostMapping("/contact")
    public String submitForm(ContactForm contactForm, Model model) {
        model.addAttribute("message", "Thanks, " + contactForm.getName());
        return "contact-success";
    }
}

Form object:

package com.example.app.form;

public class ContactForm {

    private String name;
    private String email;
    private String message;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

With Lombok, this can be simplified:

package com.example.app.form;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ContactForm {

    private String name;
    private String email;
    private String message;
}

8. Add Validation

Use Jakarta Bean Validation annotations:

package com.example.app.form;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ContactForm {

    @NotBlank
    private String name;

    @Email
    @NotBlank
    private String email;

    @NotBlank
    private String message;
}

Controller:

package com.example.app.controller;

import com.example.app.form.ContactForm;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class ContactController {

    @PostMapping("/contact")
    public String submitForm(
            @Valid ContactForm contactForm,
            BindingResult bindingResult
    ) {
        if (bindingResult.hasErrors()) {
            return "contact";
        }

        return "contact-success";
    }
}

In Spring MVC, BindingResult must immediately follow the validated argument.


9. Add Persistence with Spring Data JPA

Entity:

package com.example.app.user;

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;
}

Repository:

package com.example.app.user;

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

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

Service:

package com.example.app.user;

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<User> findAll() {
        return userRepository.findAll();
    }

    @Transactional
    public User save(User user) {
        return userRepository.save(user);
    }
}

Controller:

package com.example.app.user;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserRestController {

    private final UserService userService;

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

    @GetMapping("/api/users")
    public List<User> users() {
        return userService.findAll();
    }
}

Option B: Traditional Spring MVC Without Spring Boot

If you are building a classic Spring MVC application deployed as a WAR, you usually configure the application with Java configuration classes.

10. Add MVC Configuration

package com.example.app.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.app")
public class WebConfig implements WebMvcConfigurer {
}

@EnableWebMvc enables Spring MVC features such as request mapping, message conversion, validation support, and more.


11. Configure the DispatcherServlet

For a Servlet 3+ container, you can initialize Spring MVC without web.xml:

package com.example.app.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

Typical separation:

RootConfig     -> services, repositories, data sources, transactions
WebConfig      -> controllers, view resolvers, Spring MVC configuration
DispatcherServlet -> receives web requests

12. Add a Root Configuration

package com.example.app.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {
        "com.example.app.service",
        "com.example.app.repository"
})
public class RootConfig {
}

13. Configure Views

For JSP views in a traditional Spring MVC app:

package com.example.app.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
public class ViewConfig {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

Or add it directly to WebConfig:

package com.example.app.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.app.controller")
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

Controller:

package com.example.app.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PageController {

    @GetMapping("/")
    public String index(Model model) {
        model.addAttribute("message", "Hello Spring MVC");
        return "index";
    }
}

JSP file:

src/main/webapp/WEB-INF/views/index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>Spring MVC</title>
</head>
<body>
    <h1>${message}</h1>
</body>
</html>

14. Recommended Layering

A clean Spring MVC application usually follows this flow:

HTTP Request
    ↓
DispatcherServlet
    ↓
Controller
    ↓
Service
    ↓
Repository
    ↓
Database

Example responsibilities:

Layer Annotation Responsibility
Controller @Controller, @RestController Handle HTTP requests/responses
Service @Service Business logic and transactions
Repository @Repository or Spring Data interface Data access
Entity/Model @Entity, DTOs, form objects Data structure

15. Basic REST Endpoint Example

package com.example.app.employee;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class EmployeeController {

    @GetMapping("/employees")
    public List<String> employees() {
        return List.of("Alice", "Bob", "Charlie");
    }
}

Calling:

GET http://localhost:8080/employees

returns JSON:

["Alice", "Bob", "Charlie"]

16. Common Spring MVC Annotations

Annotation Purpose
@Controller MVC controller that usually returns a view name
@RestController REST controller returning response bodies
@RequestMapping General request mapping
@GetMapping Handles HTTP GET
@PostMapping Handles HTTP POST
@PutMapping Handles HTTP PUT
@DeleteMapping Handles HTTP DELETE
@PathVariable Reads values from URI path
@RequestParam Reads query/form parameters
@RequestBody Reads JSON/XML request body
@ResponseBody Writes method return value directly to response
@ModelAttribute Binds form/model data
@Valid Triggers Jakarta Bean Validation

17. Example REST Controller with Request Body

DTO:

package com.example.app.employee;

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

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

Controller:

package com.example.app.employee;

import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EmployeeController {

    @PostMapping("/employees")
    @ResponseStatus(HttpStatus.CREATED)
    public String createEmployee(@Valid @RequestBody CreateEmployeeRequest request) {
        return "Created employee: " + request.name();
    }
}

Example request:

POST /employees HTTP/1.1
Content-Type: application/json

{
  "name": "Alice",
  "email": "[email protected]"
}

18. Handle Errors Globally

Use @ControllerAdvice for centralized exception handling.

package com.example.app.web;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleIllegalArgument(IllegalArgumentException exception) {
        return new ErrorResponse(exception.getMessage());
    }

    public record ErrorResponse(String message) {
    }
}

19. Test a Controller

With Spring Boot, you can test MVC endpoints using MockMvc:

package com.example.app.employee;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(EmployeeController.class)
class EmployeeControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void employeesReturnsList() throws Exception {
        mockMvc.perform(get("/employees"))
                .andExpect(status().isOk())
                .andExpect(content().json("[\"Alice\",\"Bob\",\"Charlie\"]"));
    }
}

20. Practical Checklist

To build a Spring MVC web application:

  1. Add Spring MVC dependencies.
  2. Create an application entry point.
  3. Enable component scanning.
  4. Create controllers with @Controller or @RestController.
  5. Add services with @Service.
  6. Add repositories with Spring Data JPA if needed.
  7. Use constructor injection.
  8. Add validation with Jakarta Bean Validation.
  9. Configure views if returning HTML.
  10. Configure persistence if using a database.
  11. Add global exception handling.
  12. Write tests for controllers and services.
  13. Run the application and test endpoints in a browser, curl, or Postman.

For most new applications, use Spring Boot with spring-boot-starter-web. For traditional servlet-container deployment, use Java config with @EnableWebMvc and a DispatcherServlet initializer.

How do I use external configuration with Spring?

External configuration means keeping settings such as application names, URLs, ports, feature flags, credentials, or environment-specific values outside your Java code.

Spring supports this mainly through:

  • application.properties
  • application.yml
  • environment variables
  • command-line arguments
  • external property files
  • @Value
  • @ConfigurationProperties

1. Using application.properties

In a Spring or Spring Boot application, you can place configuration in:

src/main/resources/application.properties

Example:

app.name=My Spring App
app.version=1.0.0
app.description=Example application using external configuration

Then inject values with @Value:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppProperties {

    @Value("${app.name}")
    private String appName;

    @Value("${app.version}")
    private String appVersion;

    @Value("${app.description}")
    private String appDescription;

    public void printProperties() {
        System.out.println("App Name: " + appName);
        System.out.println("App Version: " + appVersion);
        System.out.println("App Description: " + appDescription);
    }
}

2. Using application.yml

You can also use YAML:

app:
  name: My Spring App
  version: 1.0.0
  description: Example application using external configuration

The same @Value expressions still work:

@Value("${app.name}")
private String appName;

3. Using @PropertySource in non-Boot Spring

If you are using plain Spring with Java configuration, register a property file using @PropertySource:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@ComponentScan("com.example.app")
@PropertySource("classpath:application.properties")
public class AppConfig {
}

Then Spring can resolve values like:

@Value("${app.name}")
private String appName;

If you are using Spring Boot, you usually do not need @PropertySource for application.properties or application.yml. Spring Boot loads them automatically.


4. Recommended Spring Boot Approach: @ConfigurationProperties

For multiple related properties, prefer @ConfigurationProperties over many @Value fields.

Example application.properties:

app.name=My Spring App
app.version=1.0.0
app.description=Example application using external configuration

Create a properties class:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {

    private String name;
    private String version;
    private String description;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Then inject it into another bean:

import org.springframework.stereotype.Service;

@Service
public class GreetingService {

    private final AppProperties appProperties;

    public GreetingService(AppProperties appProperties) {
        this.appProperties = appProperties;
    }

    public void printGreeting() {
        System.out.println("Welcome to " + appProperties.getName());
        System.out.println("Version: " + appProperties.getVersion());
        System.out.println(appProperties.getDescription());
    }
}

5. External Files Outside the JAR

You can override configuration from outside the application.

For Spring Boot:

java -jar myapp.jar --spring.config.location=file:/opt/myapp/application.properties

Or include an additional config file:

java -jar myapp.jar --spring.config.additional-location=file:/opt/myapp/

Example external file:

app.name=Production App
app.version=2.0.0
app.description=Running with production configuration

6. Environment Variables

Spring Boot can read environment variables automatically.

For example, this property:

app.name=My Spring App

Can be overridden with:

APP_NAME=Production App

Spring Boot maps environment variable names to property names using relaxed binding:

APP_NAME -> app.name
SERVER_PORT -> server.port
SPRING_DATASOURCE_URL -> spring.datasource.url

7. Command-Line Arguments

You can override properties when starting the application:

java -jar myapp.jar --app.name="Command Line App" --server.port=9090

Command-line arguments usually have high priority and override values from property files.


8. Profiles for Environment-Specific Config

Profiles let you separate configuration by environment.

Common files:

application.properties
application-dev.properties
application-test.properties
application-prod.properties

Example:

# application-prod.properties
app.name=Production App
server.port=8080

Run with a profile:

java -jar myapp.jar --spring.profiles.active=prod

Or set an environment variable:

SPRING_PROFILES_ACTIVE=prod

9. Default Values with @Value

You can provide fallback values:

@Value("${app.name:Default App}")
private String appName;

If app.name is missing, Spring uses "Default App".


10. Common Property Examples

server.port=8081

spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=myuser
spring.datasource.password=secret

logging.level.org.springframework=INFO

app.name=My Spring App
app.version=1.0.0

Summary

Use external configuration like this:

Use case Recommended approach
Simple single value @Value("${property.name}")
Group of related settings @ConfigurationProperties
Spring Boot default config application.properties or application.yml
Plain Spring Java config @PropertySource
Environment-specific config Spring profiles
Production overrides external file, environment variables, or command-line args

For most Spring Boot applications, the usual setup is:

src/main/resources/application.properties

plus a configuration class using:

@ConfigurationProperties(prefix = "app")

This keeps configuration clean, type-safe, and easy to override per environment.

How do I manage bean scope in Spring?

In Spring, bean scope controls how many instances of a bean Spring creates and how long those instances live.

By default, Spring beans are singleton scoped, meaning Spring creates one shared instance per ApplicationContext.


Common Spring Bean Scopes

Scope Meaning
singleton One shared instance per Spring container
prototype A new instance every time the bean is requested
request One instance per HTTP request
session One instance per HTTP session
application One instance per ServletContext
websocket One instance per WebSocket session

The most commonly used scopes are:

  • singleton
  • prototype
  • request
  • session

1. Singleton Scope

singleton is the default scope.

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("singleton")
public class UserService {
}

This is equivalent to:

import org.springframework.stereotype.Service;

@Service
public class UserService {
}

Spring creates only one UserService object, and every injection point receives the same instance.


Example

@Service
public class CounterService {

    private int count = 0;

    public int increment() {
        return ++count;
    }
}

Because this bean is singleton scoped, the count field is shared across the application.

That means singleton beans should usually be stateless, especially in web applications.

Prefer this:

@Service
public class PriceCalculator {

    public BigDecimal calculatePrice(BigDecimal amount) {
        return amount.multiply(new BigDecimal("1.10"));
    }
}

Avoid storing request-specific or user-specific data in singleton beans.


2. Prototype Scope

A prototype bean creates a new instance every time Spring is asked for the bean.

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class ReportBuilder {

    private String title;

    public void setTitle(String title) {
        this.title = title;
    }

    public String build() {
        return "Report: " + title;
    }
}

Each direct request to Spring for ReportBuilder creates a new object.


Prototype with @Bean

You can also define prototype scope on a @Bean method:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class AppConfig {

    @Bean
    @Scope("prototype")
    public ReportBuilder reportBuilder() {
        return new ReportBuilder();
    }
}

3. Important: Prototype Inside Singleton

A common surprise is this:

@Service
public class ReportService {

    private final ReportBuilder reportBuilder;

    public ReportService(ReportBuilder reportBuilder) {
        this.reportBuilder = reportBuilder;
    }
}

If ReportService is singleton and ReportBuilder is prototype, Spring injects one prototype instance when ReportService is created.

It does not automatically create a new ReportBuilder every time you use it.


Correct Way: Use ObjectProvider

If a singleton needs a fresh prototype instance on demand, use ObjectProvider.

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;

@Service
public class ReportService {

    private final ObjectProvider<ReportBuilder> reportBuilderProvider;

    public ReportService(ObjectProvider<ReportBuilder> reportBuilderProvider) {
        this.reportBuilderProvider = reportBuilderProvider;
    }

    public String createReport(String title) {
        ReportBuilder builder = reportBuilderProvider.getObject();
        builder.setTitle(title);
        return builder.build();
    }
}

Now each call to getObject() returns a new prototype instance.


4. Request Scope

request scope creates one bean instance per HTTP request.

This is useful in Spring MVC applications for request-specific data.

import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;

@Component
@RequestScope
public class RequestContext {

    private String correlationId;

    public String getCorrelationId() {
        return correlationId;
    }

    public void setCorrelationId(String correlationId) {
        this.correlationId = correlationId;
    }
}

Each HTTP request gets its own RequestContext.


5. Session Scope

session scope creates one bean instance per HTTP session.

import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;

@Component
@SessionScope
public class ShoppingCart {

    private final List<String> items = new ArrayList<>();

    public void addItem(String item) {
        items.add(item);
    }

    public List<String> getItems() {
        return items;
    }
}

Each user session gets its own ShoppingCart.

This is useful for things like:

  • shopping carts
  • user preferences
  • wizard-style form state

6. Using Scope Constants

Instead of writing scope names as strings, you can use Spring constants.

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ReportBuilder {
}

For singleton:

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class UserService {
}

7. XML Configuration Example

If you use XML configuration, define the scope with the scope attribute.

Singleton

<bean id="service" class="com.example.DummyService" scope="singleton"/>

Since singleton is the default, this is also valid:

<bean id="service" class="com.example.DummyService"/>

Prototype

<bean id="service" class="com.example.DummyService" scope="prototype"/>

With singleton scope, repeated calls to getBean("service") return the same object.

With prototype scope, repeated calls to getBean("service") return different objects.


8. Lifecycle Difference

Singleton beans are fully managed by Spring, including destruction callbacks.

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Service;

@Service
public class CacheService {

    @PostConstruct
    public void init() {
        System.out.println("Cache initialized");
    }

    @PreDestroy
    public void shutdown() {
        System.out.println("Cache shutting down");
    }
}

For singleton beans:

  • Spring creates the bean
  • Spring initializes it
  • Spring calls destruction callbacks when the context closes

For prototype beans:

  • Spring creates and initializes the bean
  • Spring does not manage the full destruction lifecycle after handing it out

So if a prototype bean holds resources, your application is responsible for cleanup.


9. Choosing the Right Scope

Use this as a practical guide:

Use case Recommended scope
Stateless service singleton
Repository/data access object singleton
Controller usually singleton
Stateful object created per use prototype
Request-specific data request
User-session data session

Best Practices

  1. Keep singleton beans stateless when possible.
  2. Do not store user-specific data in singleton services.
  3. Use prototype for objects that need independent state per use.
  4. Use ObjectProvider<T> when a singleton needs fresh prototype instances.
  5. Use @RequestScope for per-request web data.
  6. Use @SessionScope carefully, because session state consumes memory.
  7. Prefer annotation-based configuration in modern Spring applications.
  8. Use XML scope configuration only when working with XML-based Spring setup.

Bottom Line

You manage bean scope in Spring by declaring the scope on the bean:

@Component
@Scope("prototype")
public class MyBean {
}

or:

@Bean
@Scope("prototype")
public MyBean myBean() {
    return new MyBean();
}

If you do not specify a scope, Spring uses:

singleton

So in most applications, services, repositories, and controllers are singleton beans, while stateful per-use objects should be prototype, request, or session scoped depending on how long their state should live.

How do I inject dependencies using constructor injection?

Use constructor injection by declaring your dependency as a private final field and accepting it as a constructor parameter. Spring will create the dependency bean and pass it into the constructor automatically.

Example:

import org.springframework.stereotype.Component;

@Component
public class MyDependency {

    public void doSomething() {
        System.out.println("Dependency logic executed.");
    }
}
import org.springframework.stereotype.Service;

@Service
public class MyService {

    private final MyDependency myDependency;

    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    public void doWork() {
        myDependency.doSomething();
    }
}

Spring can inject MyDependency because:

  1. MyDependency is a Spring bean, for example annotated with @Component.
  2. MyService is also a Spring bean, for example annotated with @Service.
  3. MyService has a constructor that requires MyDependency.

In modern Spring, if the class has only one constructor, you usually do not need @Autowired:

@Service
public class MyService {

    private final MyDependency myDependency;

    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }
}

If you use Lombok, you can make it shorter:

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MyService {

    private final MyDependency myDependency;

    public void doWork() {
        myDependency.doSomething();
    }
}

Constructor injection is recommended because it makes dependencies explicit, allows fields to be final, improves testability, and prevents partially initialized objects.

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.

How do I understand the Spring ApplicationContext?

The Spring ApplicationContext is the central runtime container of a Spring application.

In simple terms:

ApplicationContext is the object that holds your Spring application together.

It knows:

  • which objects Spring should manage
  • how those objects are created
  • how dependencies are injected
  • which configuration values are available
  • which beans need lifecycle callbacks
  • which features like transactions, events, MVC, or JPA are enabled

1. The Short Definition

A Spring ApplicationContext is a container for Spring beans.

A bean is an object managed by Spring.

For example, if you have:

@Service
public class UserService {
}

Spring creates an instance of UserService and stores/manages it inside the ApplicationContext.

Conceptually:

ApplicationContext
 ├── userService
 ├── userRepository
 ├── orderService
 ├── dataSource
 ├── transactionManager
 └── many internal Spring infrastructure beans

2. Why Does Spring Need an ApplicationContext?

Without Spring, you create and connect objects yourself:

UserRepository repository = new UserRepository();
UserService service = new UserService(repository);

With Spring, you describe the objects and dependencies, and Spring does the wiring:

@Service
public class UserService {

    private final UserRepository userRepository;

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

Spring sees both classes, creates both objects, and injects UserRepository into UserService.

The place where Spring manages all of this is the ApplicationContext.


3. What the ApplicationContext Contains

The ApplicationContext contains two broad categories of things:

Your application beans

Examples:

UserController
UserService
UserRepository
OrderService
PaymentService

These are the objects you usually write.

Spring infrastructure beans

Examples:

DataSource
EntityManagerFactory
TransactionManager
MessageSource
ConversionService
HandlerMapping
BeanPostProcessor

These are objects Spring uses internally to provide features like:

  • dependency injection
  • transactions
  • validation
  • Spring MVC routing
  • database integration
  • event publishing
  • configuration loading

So the context contains both your application and the framework infrastructure around it.


4. What Happens When the ApplicationContext Starts?

When a Spring application starts, Spring creates an ApplicationContext.

A simplified startup flow looks like this:

1. Create the ApplicationContext
2. Read configuration classes, annotations, properties, and component scans
3. Discover bean definitions
4. Decide which beans should exist
5. Create singleton beans
6. Inject dependencies
7. Run bean lifecycle callbacks
8. Apply bean post-processors
9. Create proxies if needed
10. Publish startup events
11. Application is ready

A key detail: Spring first builds bean definitions, then creates actual bean instances.


5. Bean Definition vs. Bean Instance

This distinction helps a lot.

A bean definition is Spring’s recipe for creating a bean.

It includes information like:

Bean name: userService
Bean type: UserService
Scope: singleton
Dependencies: userRepository
Initialization method: maybe present
Lazy or eager: depends on configuration

A bean instance is the actual object created from that recipe.

So conceptually:

Bean definition:
  "I know how to create UserService."

Bean instance:
  new UserService(userRepository)

The ApplicationContext manages both the recipes and the actual objects.


6. Most Beans Are Singletons by Default

By default, Spring creates one shared instance of each bean per ApplicationContext.

That means this:

@Service
public class UserService {
}

usually results in one UserService object shared wherever it is injected.

So if three controllers need UserService, they all receive the same Spring-managed instance.

UserController  ─┐
AdminController ─┼──> same UserService bean
ReportController ┘

This is why Spring service beans should usually be stateless or carefully designed for thread safety.


7. You Usually Do Not Use ApplicationContext Directly

You can ask the context for a bean:

ApplicationContext context = ...;
UserService userService = context.getBean(UserService.class);

But in normal Spring application code, you usually should not do this.

Prefer dependency injection:

@Service
public class ReportService {

    private final UserService userService;

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

This is better because:

  • dependencies are explicit
  • the class is easier to test
  • the class is not tightly coupled to Spring’s container API
  • Spring can validate dependencies at startup

Use ApplicationContext#getBean() only when you truly need dynamic lookup.


8. ApplicationContext Is More Than a Bean Factory

Spring has a lower-level interface called BeanFactory.

A BeanFactory can create and manage beans.

ApplicationContext extends that idea and adds higher-level application features, such as:

  • event publishing
  • internationalization/message resolution
  • resource loading
  • environment and property access
  • integration with Spring AOP
  • web application support
  • lifecycle management

So you can think of it like this:

BeanFactory:
  Basic bean creation and dependency injection

ApplicationContext:
  BeanFactory + application-level services

In most real applications, you work with ApplicationContext, not directly with BeanFactory.


9. ApplicationContext and Dependency Injection

The most important job of the ApplicationContext is dependency injection.

Given this:

@Service
public class OrderService {

    private final PaymentService paymentService;
    private final OrderRepository orderRepository;

    public OrderService(
            PaymentService paymentService,
            OrderRepository orderRepository
    ) {
        this.paymentService = paymentService;
        this.orderRepository = orderRepository;
    }
}

Spring roughly does this:

1. See that OrderService is a bean
2. See that it needs PaymentService and OrderRepository
3. Find matching beans
4. Create those dependencies if needed
5. Call the OrderService constructor
6. Store the created OrderService bean in the context

You do not manually create the dependency graph. The ApplicationContext does.


10. ApplicationContext and Proxies

Sometimes the object you get from Spring is not the raw object you wrote.

It may be a proxy.

For example:

@Service
public class TransferService {

    @Transactional
    public void transferMoney() {
        // database work
    }
}

When @Transactional is involved, Spring may wrap TransferService in a proxy.

Conceptually:

Caller
  ↓
Transaction proxy
  ↓
Real TransferService

The proxy adds behavior around your method call:

begin transaction
call transferMoney()
commit transaction

or, if there is an error:

rollback transaction

This is why calls between Spring beans often need to go through the Spring-managed object, not through manually created objects.

The ApplicationContext is responsible for creating and managing those proxied beans.


11. ApplicationContext in a Web Application

In a Spring MVC web application, the ApplicationContext also contains web-related infrastructure.

For example:

Controller beans
Handler mappings
Request mappings
Message converters
Validation support
Exception handlers
View resolvers, if using server-side views

When a request arrives, Spring MVC uses beans from the context to decide:

GET /users/42

maps to something like:

@GetMapping("/users/{id}")
public UserDto findUser(@PathVariable Long id) {
    // ...
}

So in a web app, the context is not just managing services and repositories. It also supports request handling.


12. ApplicationContext in Spring Data JPA

With Spring Data JPA, repository interfaces are also managed through the context.

For example:

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

You do not create the implementation manually.

Spring Data creates a repository bean and registers it in the ApplicationContext.

Then you can inject it:

@Service
public class UserService {

    private final UserRepository userRepository;

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

The context knows that UserRepository is a Spring-managed bean, even though you did not write the implementation class yourself.


13. Common Types of ApplicationContext

Different application types use different context implementations.

For annotation-based configuration, you may see:

AnnotationConfigApplicationContext

For web applications, Spring uses web-aware contexts.

In Spring Boot, you often do not create the context directly. You usually start the app with:

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

SpringApplication.run(...) creates and starts the ApplicationContext for you.


14. A Small Manual Example

In a non-Boot or learning example, you might create the context manually:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);

        GreetingService greetingService =
                context.getBean(GreetingService.class);

        System.out.println(greetingService.greet("World"));
    }
}

But in real application classes, prefer injection over calling getBean().


15. Mental Model

A useful mental model is:

ApplicationContext = Spring's runtime registry and factory

It knows:
  - what beans exist
  - how to create them
  - how to connect them
  - how to configure them
  - when to initialize them
  - when to destroy them
  - whether to wrap them in proxies

Your code says:

I need a UserRepository.
I need a PaymentService.
This class is a controller.
This method should be transactional.
This property comes from configuration.

The ApplicationContext says:

I will create those objects,
wire them together,
apply the configuration,
wrap them if needed,
and make the application ready to run.

16. Practical Rules

When working with the ApplicationContext, remember these rules:

  1. Most application objects should be Spring beans.
  2. Use constructor injection for dependencies.
  3. Avoid manually calling new for services, repositories, and controllers.
  4. Avoid frequent direct use of ApplicationContext#getBean().
  5. Keep singleton beans stateless when possible.
  6. Remember that Spring may inject a proxy, not the raw class.
  7. If a bean is missing, check scanning, configuration, profiles, and conditional annotations.
  8. If multiple beans match, use @Primary or @Qualifier.

Bottom Line

The Spring ApplicationContext is the running Spring container.

It is responsible for:

  • discovering beans
  • creating beans
  • injecting dependencies
  • managing lifecycle
  • loading configuration
  • publishing events
  • supporting framework features
  • creating proxies for behavior like transactions

The shortest explanation is:

ApplicationContext is Spring’s runtime container: it creates, stores, wires, configures, and manages the objects that make up your application.