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
- Visit Spring Initializr.
- Select:
- Project: Maven Project
- Language: Java
- Spring Boot: Latest stable version (e.g., 3.x)
- Group:
com.example - Artifact:
restapi - Add Dependencies:
- Spring Web
- Spring Data JPA
- H2 Database (for in-memory testing)
- Click Generate to download the project archive.
- 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!
Related Posts:
Java
- Understanding Java Virtual Machine (JVM) Basics
- How to Use Java Arrays Effectively
- Java String Manipulation Techniques You Need to Know
- Java Control Structures: If, Switch, and Loops
- Tips for Improving Java Application Performance
- How to Deploy Java Applications on AWS Cloud
- How to Install Java JDK on Windows and Mac
- How to Write Your First Java Console Application
- How to Use Java Streams for Data Processing
- Step-by-Step Guide to Java Exception Handling
- How to Connect Java Programs to a MySQL Database
- Multithreading Basics: Creating Threads in Java
- Java Data Types Explained with Examples
- Introduction to Java Methods and Functions
- Writing Unit Tests in Java with JUnit
- Exception Handling in Java: Try, Catch, Finally Explained
- How to Debug Java Code Efficiently
- Java Interface Usage and Best Practices
- Using Java Collections: Lists, Sets, and Maps Overview
- Understanding Java Classes and Objects in Simple Terms
- How to Implement Inheritance in Java Programming
- Best Practices for Writing Clean Java Code
- How to Read and Write Files Using Java I/O Streams
- Best Practices for Java Memory Management
- Using Annotations Effectively in Java Development
- Java Programming Basics for Absolute Beginners
- How to Set Up Java Development Environment
- Essential Java Syntax for New Developers
- How to Implement Multithreading in Java
- How to Debug Java Code Using Popular IDE Tools