Updated: July 18, 2025

In the modern era of software development, RESTful APIs have become the backbone of web applications, enabling communication between the frontend and backend systems seamlessly. Java Spring Boot is one of the most popular frameworks for building robust and scalable REST APIs quickly and efficiently. This article will guide you through the process of creating RESTful APIs using Java Spring Boot, covering everything from setup to deployment.

What is Spring Boot?

Spring Boot is an extension of the Spring framework, designed to simplify the bootstrapping and development of new Spring applications. It offers a set of default configurations, embedded servers, and starter dependencies that allow developers to build production-ready applications with minimal setup.

Why Use Spring Boot for REST APIs?

  • Rapid Development: Spring Boot’s auto-configuration and starter dependencies drastically reduce setup time.
  • Embedded Server: Comes with an embedded Tomcat/Jetty server, so no need for external servers.
  • Flexible & Scalable: Easily scale your application as it grows.
  • Integration: Seamless integration with databases, security frameworks, and other tools.
  • Community Support: A large and active community ensures continuous improvements and support.

Prerequisites

Before we begin, make sure you have:

  • Installed Java Development Kit (JDK 11 or above).
  • Maven or Gradle for dependency management.
  • An IDE such as IntelliJ IDEA, Eclipse, or VS Code.
  • Basic knowledge of Java and REST concepts.

Step 1: Setting Up a Spring Boot Project

You can create a Spring Boot project either manually or by using Spring Initializr.

Using Spring Initializr

  1. Visit Spring Initializr.
  2. Select:
  3. Project: Maven Project
  4. Language: Java
  5. Spring Boot: Latest stable version (e.g., 3.x)
  6. Group: com.example
  7. Artifact: restapi
  8. Add Dependencies:
  9. Spring Web
  10. Spring Data JPA
  11. H2 Database (for in-memory testing)
  12. Click Generate to download the project archive.
  13. Extract it and open it in your IDE.

Step 2: Understanding the Project Structure

Your project will have a structure like this:

src
└─ main
├─ java
│ └─ com.example.restapi
│ ├─ RestapiApplication.java
│ ├─ controller
│ ├─ model
│ ├─ repository
│ └─ service
└─ resources
├─ application.properties

  • RestapiApplication.java: Main class to bootstrap the application.
  • controller: Contains REST controllers handling HTTP requests.
  • model: Contains domain models/entities.
  • repository: Handles data access layers.
  • service: Business logic layer (optional but recommended).
  • application.properties: Configuration file.

Step 3: Creating a Simple REST API

Let’s build a simple API for managing “Books.”

3.1 Define the Book Model

Create a Java class Book under the model package:

“`java
package com.example.restapi.model;

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

@Entity
public class Book {

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

private String title;
private String author;
private double price;

// Constructors
public Book() {}

public Book(String title, String author, double price) {
    this.title = title;
    this.author = author;
    this.price = price;
}

// Getters and Setters

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public String getTitle() {
    return title;
}

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

public String getAuthor() {
    return author;
}

public void setAuthor(String author) {
    this.author = author;
}

public double getPrice() {
    return price;
}

public void setPrice(double price) {
    this.price = price;
}

}
“`

Here, we use @Entity to mark this as a JPA entity. The id field is annotated with @Id and auto-generated.


3.2 Create the Repository Interface

Create an interface BookRepository inside the repository package:

“`java
package com.example.restapi.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import com.example.restapi.model.Book;

public interface BookRepository extends JpaRepository {
}
“`

JpaRepository provides CRUD operations out of the box.


3.3 Develop the Service Layer

Although optional in small projects, the service layer promotes clean architecture.

Create a class BookService in the service package:

“`java
package com.example.restapi.service;

import com.example.restapi.model.Book;
import com.example.restapi.repository.BookRepository;
import org.springframework.stereotype.Service;

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

@Service
public class BookService {

private final BookRepository bookRepository;

// Constructor Injection
public BookService(BookRepository bookRepository) {
    this.bookRepository = bookRepository;
}

public List<Book> getAllBooks() {
    return bookRepository.findAll();
}

public Optional<Book> getBookById(Long id) {
    return bookRepository.findById(id);
}

public Book saveBook(Book book) {
    return bookRepository.save(book);
}

public void deleteBook(Long id) {
    bookRepository.deleteById(id);
}

}
“`

This service encapsulates all data operations.


3.4 Implementing the Controller

Create a REST controller named BookController under the controller package:

“`java
package com.example.restapi.controller;

import com.example.restapi.model.Book;
import com.example.restapi.service.BookService;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

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

import java.util.List;

@RestController
@RequestMapping(“/api/books”)
public class BookController {

private final BookService bookService;

// Constructor Injection
public BookController(BookService bookService) {
    this.bookService = bookService;
}

// Get all books
@GetMapping
public List<Book> getAllBooks() {
    return bookService.getAllBooks();
}

// Get book by ID
@GetMapping("/{id}")
public ResponseEntity<Book> getBookById(@PathVariable Long id) {
    return bookService.getBookById(id)
            .map(ResponseEntity::ok)
            .orElseGet(() -> ResponseEntity.notFound().build());
}

// Create new book
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Book createBook(@RequestBody Book book) {
    return bookService.saveBook(book);
}

// Update existing book
@PutMapping("/{id}")
public ResponseEntity<Book> updateBook(@PathVariable Long id,
                                       @RequestBody Book updatedBook) {
    return bookService.getBookById(id)
            .map(book -> {
                book.setTitle(updatedBook.getTitle());
                book.setAuthor(updatedBook.getAuthor());
                book.setPrice(updatedBook.getPrice());
                Book savedBook = bookService.saveBook(book);
                return ResponseEntity.ok(savedBook);
            })
            .orElseGet(() -> ResponseEntity.notFound().build());
}

// Delete a book by ID
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteBook(@PathVariable Long id) {
    bookService.deleteBook(id);
}

}
“`

This controller exposes REST endpoints following CRUD conventions:

  • GET /api/books – Fetch all books.
  • GET /api/books/{id} – Fetch a single book by ID.
  • POST /api/books – Create a new book.
  • PUT /api/books/{id} – Update existing book details.
  • DELETE /api/books/{id} – Delete a book.

Step 4: Configure Application Properties

To keep things simple during development, use an in-memory H2 database.

Open src/main/resources/application.properties and add:

“`
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.h2.console.enabled=true

spring.jpa.show-sql=true

spring.jpa.hibernate.ddl-auto=update
“`

This configuration enables H2 console at /h2-console, shows SQL queries in logs, and automatically updates schema based on entities.


Step 5: Running and Testing Your API

Run your application via your IDE or by running:

“`bash
./mvnw spring-boot:run

or if using Gradle:

./gradlew bootRun
“`

Once running, test your endpoints using tools like Postman or curl commands.

Example curl commands:

  • Create a new book:

bash
curl -X POST http://localhost:8080/api/books \
-H "Content-Type: application/json" \
-d '{"title": "Effective Java", "author": "Joshua Bloch", "price": 45.99}'

  • Get all books:

bash
curl http://localhost:8080/api/books

  • Get a specific book by ID:

bash
curl http://localhost:8080/api/books/1

  • Update a book:

bash
curl -X PUT http://localhost:8080/api/books/1 \
-H "Content-Type: application/json" \
-d '{"title": "Effective Java (3rd Edition)", "author": "Joshua Bloch", "price": 50.00}'

  • Delete a book:

bash
curl -X DELETE http://localhost:8080/api/books/1

To access the H2 Console for database inspection, navigate to http://localhost:8080/h2-console, set JDBC URL to jdbc:h2:mem:testdb, username as sa, leave password empty, and connect.


Step 6: Adding Exception Handling

For better API usability, handle exceptions gracefully by returning meaningful error messages.

Create a class called GlobalExceptionHandler under an appropriate package (exception):

“`java
package com.example.restapi.exception;

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

import org.springframework.web.context.request.WebRequest;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleGlobalException(Exception ex, WebRequest request){
Map body = new HashMap<>();
body.put(“timestamp”, LocalDateTime.now());
body.put(“message”, ex.getMessage());
body.put(“details”, request.getDescription(false));

   return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);

}
}
“`

You can extend this class to catch custom exceptions such as resource not found or validation errors for more informative responses.


Step 7: Adding Validation to Your API

To ensure incoming data meets expectations, use validation annotations from Jakarta Bean Validation (formerly javax.validation).

Update your model as follows:

“`java
package com.example.restapi.model;

import jakarta.persistence.*;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;

@Entity
public class Book {

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

@NotBlank(message = “Title is mandatory”)
private String title;

@NotBlank(message = “Author is mandatory”)
private String author;

@Min(value = 0L, message = “The price must be positive”)
private double price;

// constructors/getters/setters unchanged…
}
“`

Modify your controller’s POST and PUT methods to enforce validation using @Valid annotation:

“`java
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Book createBook(@Valid @RequestBody Book book) {
return bookService.saveBook(book);
}

@PutMapping(“/{id}”)
public ResponseEntity updateBook(@PathVariable Long id,
@Valid @RequestBody Book updatedBook) {
// method body unchanged…
}
“`

Add exception handling specifically for validation errors in your exception handler:

“`java
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationExceptions(MethodArgumentNotValidException ex){

Map errors = new HashMap<>();

ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);

Map body = new HashMap<>();
body.put(“timestamp”, LocalDateTime.now());
body.put(“errors”, errors);

return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
“`

Validation ensures cleaner input data and makes your API more robust.


Step 8: Securing Your REST API (Optional)

In real-world applications, securing endpoints is essential. You can integrate Spring Security easily.

Add dependency in your build tool (pom.xml):

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

Configure basic authentication or JWT-based security depending on your needs—this is outside the scope of this article but can be explored further through official documentation.


Step 9: Testing Your API

Testing is crucial for production-grade applications.

You can write unit tests with JUnit and integration tests with MockMvc or TestRestTemplate. Here’s an example of unit test for controller with MockMvc:

“`java
package com.example.restapi.controller;

import com.example.restapi.model.Book;
import com.example.restapi.service.BookService;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.
;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;

import org.springframework.boot.test.mock.mockito.MockBean;

import org.springframework.http.MediaType;

@WebMvcTest(BookController.class)
public class BookControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private BookService bookService;

@Test
void testGetAllBooks() throws Exception {

  when(bookService.getAllBooks()).thenReturn(
     List.of(new Book("Java Basics", "John Doe", 29.99))
  );

  mockMvc.perform(get("/api/books"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$[0].title").value("Java Basics"));

}
}
“`

Testing improves code quality and helps detect bugs early.


Step 10: Packaging and Deployment

To build an executable .jar, run:

“`bash
./mvnw clean package

Or if using Gradle:

./gradlew clean build
“`

The generated jar in /target or /build/libs can be run via:

bash
java -jar restapi-0.0.1-SNAPSHOT.jar

Deploy it on any cloud provider or containerize it with Docker for easy deployment.


Conclusion

Building REST APIs with Java Spring Boot is straightforward due to its comprehensive ecosystem and conventions over configurations philosophy. We covered creating entities, repositories, services, controllers along with validation and exception handling — all fundamental building blocks of robust APIs.

As you grow more comfortable with these basics, explore advanced topics like asynchronous processing, caching, metrics exposure with Actuator, API versioning strategies, security best practices, and API documentation generation using OpenAPI/Swagger.

Spring Boot empowers developers to focus on business logic while abstracting away boilerplate code — making it an excellent choice for building modern backend services efficiently. Start building your next API today!