Popular Searches
Popular Course Categories
Popular Courses

Developing Spring Boot REST APIs with JPA

Developing Spring Boot REST APIs with JPA

Advanced Java Projects, Interview Preparation, and Career Readiness

Developing Spring Boot REST APIs with JPA – Detailed Notes

Course: Advance Java Training

Topic: Developing Spring Boot REST APIs with JPA

Course Link: Advance Java Training at JustAcademy

Demo Link: Register for a Course Demo

Website: JustAcademy Official Website


1. Introduction to Spring Boot REST APIs with JPA

Spring Boot REST API development combines Spring Boot, Spring Web, Spring Data JPA, and a relational database to build applications that exchange data over HTTP and persist information reliably. REST APIs allow frontend applications, mobile applications, and other services to interact with backend resources through standard HTTP methods.

Java Persistence API (JPA), now part of Jakarta Persistence, is a specification for mapping Java objects to relational database tables. Spring Data JPA builds on JPA and simplifies database access through repository interfaces, query methods, pagination, sorting, and custom queries.

For example, an employee management application can expose endpoints to create an employee, retrieve employee details, update information, and delete records. Spring Boot handles application configuration and web integration, Spring MVC handles HTTP requests, Spring Data JPA provides repository functionality, and a database stores the records.

Learning Objectives

  • Understand REST architecture and HTTP methods.
  • Understand Spring Boot and Spring Data JPA integration.
  • Create JPA entities and repository interfaces.
  • Develop REST controllers for CRUD operations.
  • Implement service-layer business logic.
  • Configure relational database connections.
  • Validate request data and handle exceptions.
  • Implement pagination, sorting, and custom queries.
  • Understand entity relationships and transaction management.
  • Test APIs and apply security and performance best practices.

2. Understanding REST APIs

REST stands for Representational State Transfer. It is an architectural style for building network-based services. REST APIs commonly expose resources through URLs and use HTTP methods to perform operations on those resources.

2.1 What Is a Resource?

A resource is an entity or object that an API exposes. Examples include employees, customers, products, orders, and courses.

For an employee management API, an employee resource might be represented by the URL:

/api/employees/101

This URL identifies the employee whose ID is 101. The HTTP method determines the operation requested on the resource.

2.2 Common HTTP Methods

Method Purpose Example
GET Retrieve a resource or collection. GET /api/employees
POST Create a new resource or submit a processing request. POST /api/employees
PUT Replace a resource representation or perform a full update according to the API contract. PUT /api/employees/101
PATCH Partially update a resource. PATCH /api/employees/101
DELETE Delete a resource. DELETE /api/employees/101

2.3 Important REST Principles

  • Resource-based URLs: Use nouns such as /employees rather than action-heavy URLs such as /getEmployees.
  • Stateless communication: Each request contains the information needed to process it, subject to the application's authentication design.
  • Standard HTTP semantics: Use methods and status codes consistently.
  • Representations: Exchange resource data through formats such as JSON.
  • Separation of concerns: Keep API handling, business logic, and persistence responsibilities distinct.
  • Consistent error responses: Return meaningful status codes and error information.

3. What Is Spring Boot?

Spring Boot simplifies the development of Spring applications by providing auto-configuration, starter dependencies, embedded server support, and production-oriented features. It reduces the amount of manual configuration normally required to build a Spring application.

3.1 Benefits of Spring Boot

  • Auto-configures many common application components.
  • Provides starter dependencies for web, persistence, validation, testing, and security.
  • Supports embedded servers for convenient application execution.
  • Integrates with Spring Data JPA and relational databases.
  • Supports external configuration through properties, YAML, and environment variables.
  • Provides integration with monitoring and health-check capabilities through Spring Boot Actuator.

3.2 Important Spring Boot Annotations

Annotation Purpose
@SpringBootApplication Combines application configuration, component scanning, and auto-configuration.
@RestController Defines a controller whose handler return values are written to the response body by default.
@RequestMapping Defines a common URL path or request mapping.
@GetMapping Maps HTTP GET requests.
@PostMapping Maps HTTP POST requests.
@PutMapping Maps HTTP PUT requests.
@PatchMapping Maps HTTP PATCH requests.
@DeleteMapping Maps HTTP DELETE requests.
@Service Marks a service-layer component.
@Repository Identifies a persistence component; Spring Data repository interfaces are managed automatically.
@Entity Marks a class as a JPA entity.
@Id Identifies the entity's primary key.
@GeneratedValue Configures primary-key generation.
@Transactional Defines transactional behavior for supported Spring-managed operations.

4. What Is JPA?

JPA is a Java specification for object-relational mapping (ORM). It defines how Java objects are mapped to relational database tables and how entities are persisted, queried, updated, and deleted.

Hibernate is a commonly used JPA implementation. Spring Boot can configure Hibernate and other persistence infrastructure automatically when the appropriate dependencies and database settings are supplied.

4.1 Object-Relational Mapping

Object-relational mapping connects Java classes and their fields with database tables and columns.

Java Concept Database Concept
Entity class Table
Entity instance Row
Field or persistent property Column
Primary-key field Primary key
Entity relationship Foreign-key or relational association
Repository operation Database access operation

4.2 Benefits of JPA

  • Maps Java objects to relational tables.
  • Reduces repetitive JDBC code for common persistence operations.
  • Supports entity relationships.
  • Provides query mechanisms through JPQL and criteria APIs.
  • Supports persistence contexts and entity lifecycle management.
  • Works with transaction management and database constraints.

5. Spring Data JPA Architecture

Spring Data JPA provides repository abstractions that reduce boilerplate persistence code. Developers define repository interfaces, and Spring Data creates implementations at runtime.

Client / Frontend
       |
       v
HTTP Request
       |
       v
Spring MVC REST Controller
       |
       v
Service Layer
       |
       v
Spring Data JPA Repository
       |
       v
JPA Provider (e.g., Hibernate)
       |
       v
Relational Database

5.1 Responsibility of Each Layer

Layer Responsibility
Controller Receives HTTP requests, validates request flow, and returns HTTP responses.
Service Implements business rules and coordinates operations.
Repository Provides database access and query operations.
Entity Represents a persistent domain object.
DTO Defines the data exchanged between the API and its clients.
Database Stores persistent application data.

Separating these responsibilities improves maintainability, testability, and consistency. A controller should not contain complex business rules or large amounts of database logic.

6. Creating a Spring Boot Project

Create a project using Spring Initializr or an IDE with Spring Boot support. Select a compatible Java version and a Spring Boot release that works with the chosen dependencies.

6.1 Required Dependencies

  • Spring Web: For REST controllers and HTTP request handling.
  • Spring Data JPA: For repository abstractions and JPA integration.
  • Database Driver: For the selected database, such as PostgreSQL, MySQL, or H2.
  • Validation: For validating request data with Jakarta Bean Validation.
  • Spring Boot Test: For automated testing.

6.2 Example Maven Dependencies

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

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

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

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

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

This example uses H2 as a convenient database for learning and testing. For production, use a suitable production database driver and secure database configuration. When using Spring Boot dependency management, versions for compatible starters are generally managed by the Spring Boot parent or BOM.

7. Creating the Main Application Class

The main class starts the Spring Boot application and triggers auto-configuration and component scanning from the application's package.

package com.example.employeeapi;

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

@SpringBootApplication
public class EmployeeApiApplication {

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

Place controllers, services, repositories, and entities in packages scanned by the main application class, or configure component and entity scanning explicitly when needed.

8. Configuring the Database

Spring Boot uses application configuration to determine which database to connect to and how JPA should behave. Connection details should be appropriate for the target environment.

8.1 Example H2 Configuration

spring.datasource.url=jdbc:h2:mem:employeedb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

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

spring.h2.console.enabled=true

This configuration creates an in-memory database for a development or demonstration environment. With create-drop, the schema is created when the application starts and dropped when the application shuts down. Data will not be retained after the in-memory database disappears.

8.2 Example PostgreSQL Configuration

spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver

spring.jpa.hibernate.ddl-auto=validate

For this example, add the PostgreSQL JDBC driver to the project and provide the required environment variables. The validate setting checks the existing schema against the entity mapping without automatically creating or altering the schema.

8.3 Important Configuration Properties

Property Purpose
spring.datasource.url Database connection URL.
spring.datasource.username Database username.
spring.datasource.password Database password.
spring.jpa.hibernate.ddl-auto Controls Hibernate schema generation behavior.
spring.jpa.show-sql Controls SQL statement logging.
spring.jpa.open-in-view Controls whether the persistence context is bound through the web request; review this setting based on application design.

Do not use automatic schema creation or destructive schema-generation settings indiscriminately in production. Prefer managed database migrations and keep secrets outside source control.

9. Creating a JPA Entity

An entity represents a persistent domain object. The following example defines an employee entity with an automatically generated identifier, name, email address, and department.

package com.example.employeeapi.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "employees")
public class Employee {

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

    @Column(nullable = false, length = 100)
    private String name;

    @Column(nullable = false, unique = true, length = 150)
    private String email;

    @Column(length = 100)
    private String department;

    public Employee() {
    }

    public Employee(String name, String email, String department) {
        this.name = name;
        this.email = email;
        this.department = department;
    }

    public Long getId() {
        return id;
    }

    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 getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }
}

9.1 Explanation of Entity Annotations

Annotation Explanation
@Entity Marks the class as a JPA entity.
@Table(name = "employees") Specifies the database table name.
@Id Marks the primary-key field.
@GeneratedValue Defines how primary-key values are generated.
@Column Configures column properties such as length, nullability, and uniqueness.

The jakarta.persistence imports are used by modern Jakarta-based Spring Boot versions. Older applications may use javax.persistence, so the imports must match the selected framework version.

10. Creating a Spring Data JPA Repository

A repository provides methods to save, retrieve, update, and delete entities. Spring Data JPA generates the implementation of the repository interface at runtime.

package com.example.employeeapi.repository;

import java.util.List;
import java.util.Optional;

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

import com.example.employeeapi.entity.Employee;

public interface EmployeeRepository
        extends JpaRepository<Employee, Long> {

    Optional<Employee> findByEmail(String email);

    List<Employee> findByDepartmentIgnoreCase(
        String department
    );

    boolean existsByEmail(String email);
}

10.1 Common JpaRepository Methods

Method Purpose
save(entity) Saves a new entity or updates a managed entity according to the persistence behavior.
findById(id) Retrieves an entity by its identifier.
findAll() Retrieves all entities; use pagination for large datasets.
existsById(id) Checks whether an entity with the specified identifier exists.
deleteById(id) Deletes an entity by identifier.
count() Returns the number of entities.
findAll(Pageable) Retrieves a page of entities.

10.2 Derived Query Methods

Spring Data JPA can derive queries from method names. For example:

List<Employee> findByNameContainingIgnoreCase(String name);

List<Employee> findByDepartment(String department);

Optional<Employee> findByEmail(String email);

Spring Data interprets supported keywords in the method name and creates an appropriate query. Choose return types that match the expected number of results. Use Optional for a single result that may not exist and a collection for multiple results.

11. Implementing the Service Layer

The service layer contains business rules and coordinates repository operations. It keeps controllers focused on HTTP concerns and makes business logic easier to test.

package com.example.employeeapi.service;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.employeeapi.entity.Employee;
import com.example.employeeapi.repository.EmployeeRepository;

@Service
@Transactional
public class EmployeeService {

    private final EmployeeRepository repository;

    public EmployeeService(EmployeeRepository repository) {
        this.repository = repository;
    }

    @Transactional(readOnly = true)
    public List<Employee> getAllEmployees() {
        return repository.findAll();
    }

    @Transactional(readOnly = true)
    public Employee getEmployeeById(Long id) {
        return repository.findById(id)
            .orElseThrow(() ->
                new EmployeeNotFoundException(id)
            );
    }

    public Employee createEmployee(Employee employee) {
        if (repository.existsByEmail(employee.getEmail())) {
            throw new DuplicateEmailException(
                employee.getEmail()
            );
        }

        return repository.save(employee);
    }

    public Employee updateEmployee(
            Long id, Employee updatedEmployee) {

        Employee existing = getEmployeeById(id);

        if (!existing.getEmail().equals(updatedEmployee.getEmail())
                && repository.existsByEmail(
                    updatedEmployee.getEmail())) {
            throw new DuplicateEmailException(
                updatedEmployee.getEmail()
            );
        }

        existing.setName(updatedEmployee.getName());
        existing.setEmail(updatedEmployee.getEmail());
        existing.setDepartment(updatedEmployee.getDepartment());

        return repository.save(existing);
    }

    public void deleteEmployee(Long id) {
        Employee existing = getEmployeeById(id);
        repository.delete(existing);
    }
}

The service refers to two custom exceptions, which are defined in the following section. The email check improves the user-facing error message, but the database should also enforce uniqueness to protect against concurrent requests.

11.1 Custom Exceptions

package com.example.employeeapi.service;

public class EmployeeNotFoundException
        extends RuntimeException {

    public EmployeeNotFoundException(Long id) {
        super("Employee not found with ID: " + id);
    }
}
package com.example.employeeapi.service;

public class DuplicateEmailException
        extends RuntimeException {

    public DuplicateEmailException(String email) {
        super("An employee already exists with email: " + email);
    }
}

12. Creating Request and Response DTOs

DTO stands for Data Transfer Object. DTOs define the information clients can submit or receive. They help avoid exposing every entity field directly and allow the API to validate incoming data independently of the database model.

12.1 Employee Request DTO

package com.example.employeeapi.dto;

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

public record EmployeeRequest(

    @NotBlank(message = "Name is required")
    @Size(max = 100, message = "Name must not exceed 100 characters")
    String name,

    @NotBlank(message = "Email is required")
    @Email(message = "Email must be valid")
    @Size(max = 150)
    String email,

    @Size(max = 100)
    String department

) {}

Java records provide a concise way to define immutable data carriers. If your Java version does not support records, use a regular class with fields, constructors, and accessors.

12.2 Employee Response DTO

package com.example.employeeapi.dto;

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

12.3 Mapping an Entity to a DTO

package com.example.employeeapi.dto;

import com.example.employeeapi.entity.Employee;

public final class EmployeeMapper {

    private EmployeeMapper() {
    }

    public static EmployeeResponse toResponse(Employee employee) {
        return new EmployeeResponse(
            employee.getId(),
            employee.getName(),
            employee.getEmail(),
            employee.getDepartment()
        );
    }
}

For larger applications, consider a dedicated mapping component or mapping library. Keep mappings explicit enough that sensitive or internal entity fields are not accidentally exposed.

13. Creating the REST Controller

A REST controller maps HTTP requests to application operations. It accepts input, calls the service layer, and returns the appropriate response and status code.

package com.example.employeeapi.controller;

import java.net.URI;
import java.util.List;

import jakarta.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.example.employeeapi.dto.EmployeeMapper;
import com.example.employeeapi.dto.EmployeeRequest;
import com.example.employeeapi.dto.EmployeeResponse;
import com.example.employeeapi.entity.Employee;
import com.example.employeeapi.service.EmployeeService;

@RestController
@RequestMapping("/api/employees")
public class EmployeeController {

    private final EmployeeService service;

    public EmployeeController(EmployeeService service) {
        this.service = service;
    }

    @GetMapping
    public List<EmployeeResponse> getAllEmployees() {
        return service.getAllEmployees()
            .stream()
            .map(EmployeeMapper::toResponse)
            .toList();
    }

    @GetMapping("/{id}")
    public EmployeeResponse getEmployeeById(
            @PathVariable Long id) {
        return EmployeeMapper.toResponse(
            service.getEmployeeById(id)
        );
    }

    @PostMapping
    public ResponseEntity<EmployeeResponse> createEmployee(
            @Valid @RequestBody EmployeeRequest request) {

        Employee employee = new Employee(
            request.name(),
            request.email(),
            request.department()
        );

        Employee saved = service.createEmployee(employee);

        return ResponseEntity
            .created(URI.create("/api/employees/" + saved.getId()))
            .body(EmployeeMapper.toResponse(saved));
    }

    @PutMapping("/{id}")
    public EmployeeResponse updateEmployee(
            @PathVariable Long id,
            @Valid @RequestBody EmployeeRequest request) {

        Employee employee = new Employee(
            request.name(),
            request.email(),
            request.department()
        );

        return EmployeeMapper.toResponse(
            service.updateEmployee(id, employee)
        );
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteEmployee(
            @PathVariable Long id) {

        service.deleteEmployee(id);
        return ResponseEntity.noContent().build();
    }
}

This controller demonstrates the basic CRUD endpoints. It uses DTOs for request and response data and returns HTTP 201 with a Location header after creation. In a production API, ensure the Location URL respects the application's context path, proxy configuration, and API versioning strategy.

14. Understanding CRUD Operations

CRUD stands for Create, Read, Update, and Delete. These operations form the foundation of many database-backed REST APIs.

Operation HTTP Method Endpoint Expected Response
Create employee POST /api/employees 201 Created
Get all employees GET /api/employees 200 OK
Get employee by ID GET /api/employees/{id} 200 OK
Update employee PUT /api/employees/{id} 200 OK
Delete employee DELETE /api/employees/{id} 204 No Content

14.1 Create an Employee

Send a POST request with JSON data to /api/employees.

{
  "name": "Amit Sharma",
  "email": "[email protected]",
  "department": "Engineering"
}

A successful response should use HTTP 201 Created and return the created employee representation, with a Location header pointing to the new resource.

14.2 Retrieve an Employee

Send a GET request to:

GET /api/employees/1

Example JSON response:

{
  "id": 1,
  "name": "Amit Sharma",
  "email": "[email protected]",
  "department": "Engineering"
}

14.3 Update an Employee

Send a PUT request to /api/employees/1 with the complete update representation expected by the API.

{
  "name": "Amit Sharma",
  "email": "[email protected]",
  "department": "Platform Engineering"
}

The service first verifies that the employee exists, checks the email uniqueness rule, and updates the entity fields. If the ID does not exist, the API should return a not-found response.

14.4 Delete an Employee

Send a DELETE request to:

DELETE /api/employees/1

A successful deletion commonly returns HTTP 204 No Content. The response body should be empty.

15. Request Validation

Validation prevents invalid or incomplete data from entering the business layer. Spring Boot integrates with Jakarta Bean Validation when the validation starter and provider are present.

15.1 Using @Valid

In the controller, place @Valid before the request DTO parameter to trigger validation:

@PostMapping
public ResponseEntity<EmployeeResponse> createEmployee(
        @Valid @RequestBody EmployeeRequest request) {
    // Process valid request data.
}

15.2 Common Validation Annotations

Annotation Purpose
@NotNull Rejects null values.
@NotBlank Rejects null, empty, or whitespace-only character sequences.
@NotEmpty Rejects null or empty strings, collections, maps, or arrays where supported.
@Email Checks whether a string resembles a valid email address.
@Size Restricts the size or length of supported values.
@Min Defines a minimum numeric value.
@Max Defines a maximum numeric value.
@Positive Requires a positive numeric value.

Validation annotations check the request according to their defined constraints. Business rules such as whether an email is already registered should be checked separately and supported by database constraints where appropriate.

16. Centralized Exception Handling

Exception handling translates application errors into consistent HTTP responses. Without centralized handling, clients may receive inconsistent error messages or implementation details that should not be exposed.

16.1 Creating an Error Response DTO

package com.example.employeeapi.dto;

import java.time.Instant;
import java.util.Map;

public record ApiError(
    Instant timestamp,
    int status,
    String error,
    String message,
    String path,
    Map<String, String> validationErrors
) {}

16.2 Global Exception Handler

package com.example.employeeapi.exception;

import java.time.Instant;
import java.util.Map;
import java.util.stream.Collectors;

import jakarta.servlet.http.HttpServletRequest;

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

import com.example.employeeapi.dto.ApiError;
import com.example.employeeapi.service.DuplicateEmailException;
import com.example.employeeapi.service.EmployeeNotFoundException;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EmployeeNotFoundException.class)
    public ResponseEntity<ApiError> handleNotFound(
            EmployeeNotFoundException ex,
            HttpServletRequest request) {

        return buildError(
            HttpStatus.NOT_FOUND,
            ex.getMessage(),
            request.getRequestURI(),
            Map.of()
        );
    }

    @ExceptionHandler(DuplicateEmailException.class)
    public ResponseEntity<ApiError> handleDuplicateEmail(
            DuplicateEmailException ex,
            HttpServletRequest request) {

        return buildError(
            HttpStatus.CONFLICT,
            ex.getMessage(),
            request.getRequestURI(),
            Map.of()
        );
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiError> handleValidation(
            MethodArgumentNotValidException ex,
            HttpServletRequest request) {

        Map<String, String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .collect(Collectors.toMap(
                error -> error.getField(),
                error -> error.getDefaultMessage() == null
                    ? "Invalid value"
                    : error.getDefaultMessage(),
                (first, second) -> first
            ));

        return buildError(
            HttpStatus.BAD_REQUEST,
            "Validation failed",
            request.getRequestURI(),
            errors
        );
    }

    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<ApiError> handleDataIntegrity(
            DataIntegrityViolationException ex,
            HttpServletRequest request) {

        return buildError(
            HttpStatus.CONFLICT,
            "The request conflicts with existing data",
            request.getRequestURI(),
            Map.of()
        );
    }

    private ResponseEntity<ApiError> buildError(
            HttpStatus status,
            String message,
            String path,
            Map<String, String> validationErrors) {

        ApiError error = new ApiError(
            Instant.now(),
            status.value(),
            status.getReasonPhrase(),
            message,
            path,
            validationErrors
        );

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

This handler demonstrates common error mappings. Review exception ordering and database error handling for your application's needs. Avoid returning raw SQL, stack traces, internal file paths, or confidential data to clients.

16.3 Common HTTP Status Codes

Status Meaning Typical Use
200 OK Request succeeded. Successful GET or update with a response body.
201 Created Resource was created. Successful POST creating a resource.
204 No Content Request succeeded without a response body. Successful deletion.
400 Bad Request Request is invalid. Validation or malformed JSON errors.
401 Unauthorized Authentication is missing or invalid. Request requires valid authentication.
403 Forbidden Request is not permitted. Authenticated user lacks required permission.
404 Not Found Resource does not exist. Employee ID is not found.
409 Conflict Request conflicts with current resource state. Duplicate email or uniqueness conflict.
500 Internal Server Error Unexpected server-side failure. Unhandled application error.

17. Pagination and Sorting

Returning every database record in one response can consume excessive memory and network bandwidth. Pagination limits the amount of data returned per request. Sorting controls the order of the returned results.

17.1 Pagination with Pageable

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public Page<Employee> getEmployees(Pageable pageable) {
    return repository.findAll(pageable);
}

Spring Data JPA supports the Pageable abstraction. A controller can accept page and size parameters and pass them to the service layer.

17.2 Example Controller Endpoint

@GetMapping("/paged")
public Page<EmployeeResponse> getPagedEmployees(
        Pageable pageable) {

    return service.getEmployees(pageable)
        .map(EmployeeMapper::toResponse);
}

This example requires the service to return a Page<Employee> and map it to response DTOs. A request may look like:

GET /api/employees/paged?page=0&size=10&sort=name,asc

Page numbering typically starts at zero. Validate or cap the requested page size so clients cannot request an excessively large response.

17.3 Page, Slice, and List

Return Type Behavior Use Case
Page<T> Includes page metadata and total-element information. When clients need total counts and page totals.
Slice<T> Indicates whether more data is available without requiring the full total count. Large datasets where next-page navigation is sufficient.
List<T> Returns a collection without page metadata. Small, bounded results.

18. Custom Queries with Spring Data JPA

Spring Data JPA supports query derivation from method names and explicit queries using @Query. Choose the query method that makes the operation clear and maintainable.

18.1 Derived Query Example

List<Employee> findByDepartmentIgnoreCase(
    String department
);

List<Employee> findByNameContainingIgnoreCase(
    String name
);

18.2 JPQL Query Example

import java.util.List;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

@Query("select e from Employee e where lower(e.department) = lower(:department)")
List<Employee> searchByDepartment(
    @Param("department") String department
);

JPQL queries refer to entity classes and their properties rather than directly referring to physical table and column names. The example query can be added to EmployeeRepository.

18.3 Native SQL Query Example

@Query(
    value = "SELECT * FROM employees WHERE department = :department",
    nativeQuery = true
)
List<Employee> findByDepartmentNative(
    @Param("department") String department
);

Native SQL can be useful when database-specific functionality is required. However, it ties the query more closely to a particular schema or database dialect. Use parameter binding instead of concatenating user input into SQL strings.

19. Entity Relationships

Real-world applications often contain related entities. JPA provides relationship annotations to model these connections.

Relationship Meaning Example
@OneToOne One entity is associated with one other entity. Employee and employee profile.
@OneToMany One entity is associated with multiple entities. Department and employees.
@ManyToOne Many entities are associated with one entity. Many employees belong to one department.
@ManyToMany Multiple entities are associated with multiple entities. Students and courses.

19.1 Many-to-One Example

@Entity
@Table(name = "departments")
public class Department {

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

    @Column(nullable = false, unique = true)
    private String name;

    public Department() {
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

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

An employee can reference a department through a many-to-one association:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;

In a complete entity, add the necessary imports, constructors, and accessors. The association maps the employee's department reference to a foreign key column named department_id.

19.2 Relationship Best Practices

  • Understand owning and inverse sides of relationships.
  • Use lazy loading deliberately and avoid accidental database queries during serialization.
  • Return DTOs instead of serializing complex entity graphs directly.
  • Use cascade operations only when their lifecycle semantics are appropriate.
  • Review orphan removal and deletion behavior carefully.
  • Avoid infinite JSON recursion in bidirectional relationships.
  • Use database foreign-key constraints to preserve referential integrity.

20. Transaction Management

A transaction groups database operations into a logical unit of work. Transactions help maintain consistency when multiple operations must succeed or fail together.

20.1 Using @Transactional

@Transactional
public Employee updateEmployee(
        Long id, Employee updatedEmployee) {

    Employee existing = getEmployeeById(id);

    existing.setName(updatedEmployee.getName());
    existing.setEmail(updatedEmployee.getEmail());
    existing.setDepartment(updatedEmployee.getDepartment());

    return repository.save(existing);
}

Spring's transaction management coordinates the operation according to the configured transaction manager. For managed entities, changes may be persisted during transaction commit even without an explicit repository save, though calling save may make the intent clearer in some designs.

20.2 Transaction Best Practices

  • Define transaction boundaries around coherent business operations.
  • Keep transactions short to reduce lock duration.
  • Understand default rollback behavior, especially for checked versus unchecked exceptions.
  • Avoid long-running network calls inside database transactions when possible.
  • Use read-only transactions for appropriate read operations.
  • Test rollback behavior for operations that modify multiple records.

21. Database Constraints and Data Integrity

Application-level validation improves error messages, but the database remains an important enforcement point for data integrity. Constraints protect data when multiple requests execute concurrently or when data is written outside the API.

21.1 Common Constraints

  • Primary key: Uniquely identifies each row.
  • Unique constraint: Prevents duplicate values or duplicate combinations.
  • Not-null constraint: Prevents missing values in required columns.
  • Foreign key: Ensures referenced rows exist.
  • Check constraint: Enforces a database-level condition when supported.

For example, the employee email column can be unique so two concurrent requests cannot create duplicate employee email addresses. The API should translate the resulting database constraint violation into a useful and safe response.

22. API Security

A REST API should be protected according to its data sensitivity and business requirements. Spring Boot applications commonly use Spring Security to authenticate requests and enforce authorization rules.

22.1 Essential Security Measures

  • Authenticate users or service clients where required.
  • Authorize access to resources based on roles, scopes, ownership, or other policies.
  • Use HTTPS in deployed environments.
  • Validate incoming data and enforce size limits.
  • Keep database credentials and API secrets out of source control.
  • Use parameterized queries and avoid unsafe SQL construction.
  • Configure CORS only for trusted origins when browser access requires it.
  • Protect state-changing operations against relevant CSRF risks based on the authentication model.
  • Do not expose passwords, tokens, or sensitive entity fields in API responses.
  • Log security events carefully without logging secrets.

22.2 Authentication vs. Authorization

Concept Meaning Example
Authentication Verifies the identity of a caller. A user signs in with valid credentials.
Authorization Determines what an authenticated caller may do. Only an administrator may delete employees.

23. Testing Spring Boot REST APIs

Testing verifies that endpoints, service logic, validation, and database operations behave as expected. A useful testing strategy includes unit tests, MVC tests, repository tests, and integration tests.

23.1 Types of Tests

Test Type Purpose
Unit Test Tests an isolated component, such as a service, with dependencies mocked when appropriate.
Controller Test Tests request mapping, response handling, validation, and controller behavior.
Repository Test Tests JPA queries and persistence behavior against a test database.
Integration Test Tests the interaction among the application layers and database configuration.
End-to-End Test Exercises the application through its public API in a realistic environment.

23.2 Example Controller Integration Test

package com.example.employeeapi;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
class EmployeeControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void createsEmployee() throws Exception {
        mockMvc.perform(post("/api/employees")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {
                      "name": "Amit Sharma",
                      "email": "[email protected]",
                      "department": "Engineering"
                    }
                    """))
            .andExpect(status().isCreated())
            .andExpect(header().exists("Location"))
            .andExpect(jsonPath("$.name").value("Amit Sharma"))
            .andExpect(jsonPath("$.email")
                .value("[email protected]"));
    }
}

This test starts the Spring application context and uses MockMvc to exercise the web layer. Use an isolated test database or reliable cleanup strategy so test data does not leak between tests.

23.3 Important Test Scenarios

  • Create an employee with valid data.
  • Reject a request with a blank name or invalid email.
  • Retrieve an existing employee.
  • Return 404 for an employee that does not exist.
  • Reject duplicate email addresses.
  • Update an employee successfully.
  • Delete an employee and verify it no longer exists.
  • Verify pagination and sorting.
  • Verify authorization for protected endpoints.
  • Verify that database failures are handled safely.

24. Running and Testing the API

Run the application using Maven:

mvn spring-boot:run

Or build the application and run the generated executable JAR:

mvn clean package
java -jar target/employee-api-0.0.1-SNAPSHOT.jar

The JAR filename depends on the project's artifact and version configuration.

24.1 Testing with curl

Create an employee:

curl -i -X POST http://localhost:8080/api/employees \
  -H "Content-Type: application/json" \
  -d '{"name":"Amit Sharma","email":"[email protected]","department":"Engineering"}'

Retrieve all employees:

curl http://localhost:8080/api/employees

Retrieve one employee:

curl http://localhost:8080/api/employees/1

Update an employee:

curl -i -X PUT http://localhost:8080/api/employees/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Amit Sharma","email":"[email protected]","department":"Platform"}'

Delete an employee:

curl -i -X DELETE http://localhost:8080/api/employees/1

These commands assume the application is running locally on port 8080 and that the employee ID exists for the update and delete requests.

25. Common Errors and Troubleshooting

Error Possible Cause Solution
Application fails to start. Missing dependency, invalid configuration, or database connection failure. Review startup logs, dependency versions, and database settings.
404 Not Found. Incorrect URL or resource ID, or the endpoint mapping is missing. Check controller mappings, base paths, and data existence.
400 Bad Request. Malformed JSON or failed request validation. Verify JSON syntax, content type, and validation constraints.
415 Unsupported Media Type. Incorrect or missing Content-Type header. Send the media type required by the endpoint, commonly application/json.
500 Internal Server Error. Unhandled exception or database failure. Inspect server logs and configure safe centralized exception handling.
Duplicate-key error. Unique database constraint is violated. Validate the input and translate the conflict into an appropriate API response.
Table does not exist. Schema was not created or migration was not applied. Review schema migration and environment configuration.
JSON serialization error. Entity relationships contain cycles or lazy-loaded state is accessed unexpectedly. Use DTOs and design relationship fetching deliberately.
Database connection refused. Incorrect host, port, credentials, or database availability. Verify connection settings and database status.
Unexpected duplicate data. Application-level checks are not backed by database constraints. Add appropriate database constraints and handle concurrency safely.

26. Performance Optimization

As an API grows, database access patterns and serialization can affect response time and resource usage. Performance work should be guided by measurements rather than assumptions.

26.1 Common Optimization Techniques

  • Use pagination for large result sets.
  • Retrieve only the fields needed by the API when appropriate.
  • Review generated SQL and database query plans.
  • Add indexes based on query patterns and measured performance.
  • Avoid unnecessary eager loading of entity relationships.
  • Use DTO projections or tailored queries for read-heavy endpoints.
  • Configure connection-pool limits based on database capacity.
  • Consider caching only when the consistency requirements are understood.
  • Use batch operations carefully when inserting or updating large amounts of data.
  • Monitor API latency, database latency, error rates, and resource usage.

26.2 Avoid the N+1 Query Problem

The N+1 query problem occurs when the application executes one query to retrieve a collection and then additional queries for related data for each returned entity. It can cause significant performance degradation.

Possible solutions include carefully designed fetch joins, entity graphs, projections, and batch fetching. Choose a solution that matches the API's data needs and verify the SQL generated by the persistence provider.

27. API Documentation and Versioning

API documentation helps frontend developers, testers, and other service consumers understand the available endpoints, request formats, responses, and error conditions.

27.1 Documentation Should Include

  • Endpoint paths and HTTP methods.
  • Request headers and authentication requirements.
  • Request and response JSON examples.
  • Validation rules and error response formats.
  • Pagination, filtering, and sorting parameters.
  • Expected HTTP status codes.

27.2 API Versioning

When an API contract changes incompatibly, versioning can help clients migrate safely. One common approach is URI versioning:

/api/v1/employees
/api/v2/employees

Other approaches include version information in headers or media types. Select a consistent policy and document how long older API versions will remain supported.

28. Practical Project: Employee Management REST API

Build a complete employee management API using Spring Boot, Spring Data JPA, and a relational database.

Project Requirements

  • Create a new employee.
  • Retrieve all employees with pagination.
  • Retrieve employee details by ID.
  • Update employee information.
  • Delete an employee.
  • Search employees by department or name.
  • Validate names and email addresses.
  • Prevent duplicate email addresses.
  • Return meaningful HTTP status codes and error responses.
  • Write unit and integration tests.
  • Configure database connection properties through environment variables.
  • Document the endpoints and sample requests.

Suggested Project Structure

src/main/java/com/example/employeeapi/
├── EmployeeApiApplication.java
├── controller/
│   └── EmployeeController.java
├── dto/
│   ├── EmployeeRequest.java
│   ├── EmployeeResponse.java
│   ├── EmployeeMapper.java
│   └── ApiError.java
├── entity/
│   └── Employee.java
├── exception/
│   └── GlobalExceptionHandler.java
├── repository/
│   └── EmployeeRepository.java
└── service/
    ├── EmployeeService.java
    ├── EmployeeNotFoundException.java
    └── DuplicateEmailException.java

Project Completion Checklist

  • ☑ Application starts successfully.
  • ☑ Database connectivity is configured.
  • ☑ Employee entity and repository are implemented.
  • ☑ Service layer contains the business logic.
  • ☑ CRUD endpoints return appropriate status codes.
  • ☑ Request validation and global exception handling work correctly.
  • ☑ Pagination and search operations work as expected.
  • ☑ Duplicate email constraints are enforced.
  • ☑ Integration tests pass.
  • ☑ API documentation and setup instructions are complete.

29. Common Development Mistakes to Avoid

  • Placing all business logic directly inside controller methods.
  • Returning JPA entities directly without considering API boundaries and serialization.
  • Exposing sensitive database fields through response objects.
  • Using findAll() without pagination for an unbounded dataset.
  • Ignoring database constraints and concurrent requests.
  • Using destructive schema-generation settings in production.
  • Building SQL queries through unsafe string concatenation.
  • Returning HTTP 200 for every operation, including failures.
  • Swallowing exceptions without logging or appropriate error responses.
  • Failing to test validation, missing records, and database errors.
  • Hardcoding production credentials in configuration files.
  • Ignoring transaction boundaries and entity relationship fetching behavior.

30. Interview Questions and Answers

Q1. What is Spring Boot?

Answer: Spring Boot simplifies Spring application development through auto-configuration, starter dependencies, embedded server support, and production-oriented integration.

Q2. What is a REST API?

Answer: A REST API exposes resources through URLs and uses HTTP methods and representations to let clients interact with those resources.

Q3. What is JPA?

Answer: JPA is a Java specification for mapping Java objects to relational database structures and managing their persistence.

Q4. What is the difference between JPA and Hibernate?

Answer: JPA defines the persistence specification, while Hibernate is a widely used implementation of that specification.

Q5. What is Spring Data JPA?

Answer: Spring Data JPA provides repository abstractions and query-generation capabilities that simplify access to JPA-managed data.

Q6. What is an entity?

Answer: An entity is a Java class mapped to a persistent data model, commonly a relational database table.

Q7. What is JpaRepository?

Answer: It is a Spring Data interface that provides common CRUD operations, pagination, sorting, and other repository functionality.

Q8. What is the purpose of the service layer?

Answer: The service layer implements business rules and coordinates operations between controllers and repositories.

Q9. What is the difference between @Controller and @RestController?

Answer: @Controller is commonly used for MVC controllers and views. @RestController combines controller semantics with response-body handling by default.

Q10. What is @RequestBody?

Answer: It instructs Spring MVC to read the HTTP request body and convert it into the specified Java object using an appropriate message converter.

Q11. What is the purpose of @Valid?

Answer: It triggers validation of a supported request object against its declared validation constraints.

Q12. What is the difference between PUT and PATCH?

Answer: PUT generally represents replacing a resource representation, while PATCH represents applying a partial modification according to the API contract.

Q13. What is @Transactional?

Answer: It declares transaction boundaries for supported Spring-managed operations so that database work can commit or roll back according to the transaction rules.

Q14. What is the N+1 query problem?

Answer: It occurs when an initial query is followed by additional queries for related data, often one for each result. Fetch planning, projections, and batching can help address it.

Q15. Why should DTOs be used?

Answer: DTOs define the API's data contract, help validate inputs, reduce accidental exposure of internal entity fields, and separate persistence models from external representations.

Q16. What is the purpose of @RestControllerAdvice?

Answer: It provides centralized exception handling for REST controllers, allowing consistent error responses across endpoints.

Q17. How do you implement pagination?

Answer: Use Spring Data's Pageable abstraction and return a suitable Page or Slice response. Validate requested page sizes.

Q18. Why are database constraints important?

Answer: Constraints enforce data integrity at the database level, including under concurrent requests and when data is modified outside the API.

Q19. How do you test a Spring Boot REST API?

Answer: Use unit tests for isolated logic, MVC tests for request handling, repository tests for persistence, and integration tests for end-to-end interactions among application layers.

Q20. What is the purpose of Spring Security?

Answer: Spring Security provides authentication, authorization, and related security features for Spring applications.

31. Practice Exercises

  1. Create a Spring Boot project with Spring Web and Spring Data JPA.
  2. Create a Product entity with ID, name, price, and category.
  3. Implement a repository extending JpaRepository.
  4. Create service methods for all CRUD operations.
  5. Build a REST controller with GET, POST, PUT, and DELETE endpoints.
  6. Validate product names and prices.
  7. Return 404 when a product does not exist.
  8. Prevent duplicate product codes using a database constraint.
  9. Add pagination and sorting to the product list endpoint.
  10. Write a custom query to search products by category.
  11. Create integration tests for successful and unsuccessful requests.
  12. Configure a production database through environment variables.
  13. Add authentication and role-based authorization to protected endpoints.
  14. Document the API with request and response examples.

32. Quick Revision Checklist

  • ☑ Understand REST resources and HTTP methods.
  • ☑ Know the roles of Spring Boot, Spring MVC, JPA, and Hibernate.
  • ☑ Create entity classes with appropriate mappings.
  • ☑ Use Spring Data JPA repository interfaces.
  • ☑ Separate controller, service, repository, entity, and DTO responsibilities.
  • ☑ Implement CRUD endpoints with suitable HTTP status codes.
  • ☑ Validate requests using Jakarta Bean Validation.
  • ☑ Configure centralized exception handling.
  • ☑ Use pagination and sorting for large datasets.
  • ☑ Write derived queries and custom JPQL queries.
  • ☑ Understand transactions and entity relationships.
  • ☑ Apply security, testing, and performance best practices.

33. Additional Learning Resources

34. Conclusion

Developing Spring Boot REST APIs with JPA combines HTTP request handling, business logic, object-relational mapping, and database persistence into a maintainable application architecture. Spring Boot simplifies configuration, Spring MVC handles API endpoints, Spring Data JPA reduces persistence boilerplate, and JPA entities map domain objects to relational tables.

To build reliable APIs, use clear service boundaries, request and response DTOs, validation, database constraints, transaction management, centralized exception handling, and appropriate HTTP status codes. Add pagination for large datasets, test important success and failure cases, secure sensitive endpoints, and monitor database performance as the application grows.

Previous Next
whatsapp