In previous lectures we covered the basics of Spring MVC: the architecture, key annotations, and routing fundamentals. Now it's time to put that knowledge into practice and build a full REST API.
Let's build a controller to manage books in a library. Our API will be able to get a list of books, find a book by ID, add new books, update their information, and delete them from the store. Along the way we'll implement all the necessary CRUD operations, handle HTTP methods (GET, POST, PUT, DELETE), return data as JSON, and add basic testing.
A REST API is the backbone of modern web development. Whether you're building a web app, a mobile app, or a microservices architecture, you'll be creating and maintaining API endpoints. Let's learn how to do it right!
1. Project setup
Dependencies. First, let's make sure our Spring Boot project is configured correctly. Include the necessary dependencies in pom.xml:
<dependencies>
<!-- Spring Web for MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Dependency for JSON handling -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
Alright, now let's move on to creating the controller.
2. Description of the Book entity
Before implementing the controller, we need the entity it will work with. Let's create a simple Book class.
package com.example.demo.model;
public class Book {
private Long id;
private String title;
private String author;
// Constructors
public Book() {}
public Book(Long id, String title, String author) {
this.id = id;
this.title = title;
this.author = author;
}
// 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;
}
}
3. Creating the REST controller
Now we're ready to create our controller. It will handle HTTP requests to manage books. Use the @RestController annotation to tell Spring that this class handles REST requests.
package com.example.demo.controller;
import com.example.demo.model.Book;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController {
private final List<Book> books = new ArrayList<>();
// Data initialization
public BookController() {
books.add(new Book(1L, "Clean Code", "Robert C. Martin"));
books.add(new Book(2L, "Effective Java", "Joshua Bloch"));
}
// Get all books
@GetMapping
public List<Book> getAllBooks() {
return books;
}
// Get a book by ID
@GetMapping("/{id}")
public Book getBookById(@PathVariable Long id) {
return books.stream()
.filter(book -> book.getId().equals(id))
.findFirst()
.orElseThrow(() -> new RuntimeException("Book not found"));
}
// Create a new book
@PostMapping
public Book createBook(@RequestBody Book book) {
books.add(book);
return book;
}
// Update a book
@PutMapping("/{id}")
public Book updateBook(@PathVariable Long id, @RequestBody Book updatedBook) {
Book book = getBookById(id);
book.setTitle(updatedBook.getTitle());
book.setAuthor(updatedBook.getAuthor());
return book;
}
// Delete a book
@DeleteMapping("/{id}")
public void deleteBook(@PathVariable Long id) {
Book book = getBookById(id);
books.remove(book);
}
}
4. Controller code breakdown
Main controller methods
- GET
/booksReturns the list of all books. Super simple — it just returns thebookslist. - GET
/books/{id}Finds a book by ID. If the book isn't found, an exception is thrown (you can improve error handling later). - POST
/booksCreates a new book. We pass aBookobject in the request body (the@RequestBodyannotation). - PUT
/books/{id}Updates an existing book by ID. Here we first find the book by ID and then update its data. - DELETE
/books/{id}Deletes a book by ID. We find the book first, then remove it from the list.
Annotation details
@RequestMapping("/books"): base path for all requests.@GetMapping,@PostMapping,@PutMapping,@DeleteMapping: handle HTTP requests.@PathVariableextracts parameters from the request path.@RequestBodyreads the request body and converts it into a Java object.
5. Testing the controller
You can test the API using tools like Postman or plain cURL. Example requests:
GET all books
GET http://localhost:8080/books
GET book by ID
GET http://localhost:8080/books
POST a new book
POST http://localhost:8080/books
Content-Type: application/json
{
"id": 3,
"title": "The Pragmatic Programmer",
"author": "Andrew Hunt"
}
DELETE a book
DELETE http://localhost:8080/books/1
6. Common mistakes and how to fix them
- 404 Not Found on
GET /books/{id}This happens if the requested book isn't in the list. Make surebooksis initialized properly. - JSON serialization error Check that all object fields have getters/setters, otherwise Jackson won't be able to serialize the object to JSON.
- 415 Unsupported Media Type on
POSTorPUTMake sure the request header includesContent-Type: application/json.
Now we have a fully functional REST controller! In the next lecture we'll get into the Thymeleaf templating engine so you can learn how to build nice HTML pages for our web apps.
GO TO FULL VERSION