Build a REST API with Spring Boot

Build a Book Catalog REST API with full CRUD operations in Java.

Introduction

30 Second Summary

Every time you search for a book online, add one to a wishlist, or remove it from your cart, a server somewhere is handling that request. These servers expose APIs that let different applications talk to each other.

In this project, you will build a Book Catalog REST API using Spring Boot that can create, read, update, and delete books from a collection.

What You'll Build

A REST API server built with Spring Boot that manages a collection of books through HTTP endpoints.

By the end of this project, you'll have:

  • A running Spring Boot application that responds to HTTP requests on localhost:8080.
  • A Book model class that automatically converts to and from JSON.
  • A REST controller handling all CRUD operations (Create, Read, Update, Delete) with pre-loaded sample data.
  • Secret Mission: Add a search endpoint that filters books by author name.

Are there any prerequisites?

You only need a basic understanding of Java syntax (variables, classes, and methods). No prior Spring or REST experience is required.

Before We Start

Before We Start...

Set Up Your Development Environment

Before you can build a REST API, you need two things: a Java Development Kit (JDK) to compile and run Java code, and an IDE to write it in.

In this step, you will install both tools and verify they are working. Once your environment is ready, you can jump straight into generating your Spring Boot project.

In this step, get ready to:
  • Install JDK 17 or later and verify it in the terminal.
  • Download and install IntelliJ IDEA (or VS Code with the Spring Boot Extension Pack).
  • Verify your IDE opens successfully.
Install JDK 17 or later

Spring Boot requires Java SDK v17 or higher to compile and run your application. The JDK includes the Java compiler, runtime, and standard libraries.

  • Open a terminal to check if Java is already installed. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Check your Java version by running this command:
java -version

✔️ I see version 17 or higher

You should see output that includes a version number like 17.0.x, 21.0.x, or 25.0.x. Any of these work for this project.

Your JDK is ready. Skip ahead to the next substep.

ⓧ I see an older version

If the version number is below 17 (for example 11.0.x or 1.8.x), you need to install a newer JDK. Your existing version will not work with Spring Boot.

  • Visit the Adoptium download page at https://adoptium.net/temurin/releases/.
  • Select JDK 17 - LTS from the version filter.
  • Download the installer for your operating system (.dmg for macOS, .msi for Windows).
  • Run the installer and follow the prompts. Accept all default settings.
  • Close and reopen your terminal, then run java -version again to confirm the new version appears.

Why Adoptium over Oracle JDK?

Eclipse Temurin (from Adoptium) is a free, open-source JDK with no licensing restrictions. Oracle JDK 17 changed its license terms in 2024, so Adoptium is the simplest choice for development.

ⓧ Command not found

If you see a message like "java is not recognized" or "command not found", Java is not installed on your machine yet.

  • Visit the Adoptium download page at https://adoptium.net/temurin/releases/.
  • Select JDK 17 - LTS from the version filter.
  • Download the installer for your operating system (.dmg for macOS, .msi for Windows).
  • Run the installer and follow the prompts. Accept all default settings.
  • Close and reopen your terminal, then run java -version again to confirm Java is now installed.

What is Adoptium?

Eclipse Adoptium provides free, production-ready OpenJDK builds called Eclipse Temurin. These are the same Java binaries used by millions of developers worldwide, with no licensing fees.

What is a JDK?

The Java Development Kit includes everything you need to write and run Java programs. It contains the Java compiler (which turns your code into runnable programs), the Java runtime (which executes those programs), and a library of pre-built tools.

Download and install IntelliJ IDEA

You need an IDE (Integrated Development Environment) to write, organize, and run your Java code. IntelliJ IDEA is the most popular Java IDE and includes built-in support for Spring Boot projects.

What about VS Code?

If you already use VS Code, you can install the Spring Boot Extension Pack instead. IntelliJ IDEA is recommended for this project because it has stronger Java support out of the box.

  • Open your browser and navigate to https://www.jetbrains.com/idea/download/.
  • Click Download for your operating system (macOS or Windows).
  • Run the downloaded installer and follow the setup prompts. Accept all default settings.

Is IntelliJ IDEA free?

IntelliJ IDEA is now a single unified product. Core Java and Kotlin development features are completely free with no subscription required. You will see a 30-day Ultimate trial offer when you first open it. You can dismiss this and continue using the free features.

Installer not starting?

On macOS, you may need to allow the app in System Settings > Privacy & Security. On Windows, right-click the installer and select Run as administrator.

Still stuck? Help me install IntelliJ IDEA on my machine.

Verify your IDE is working
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type IntelliJ and press Enter to open the application.
  • When the Welcome screen appears, you have confirmed IntelliJ IDEA is installed correctly.

If IntelliJ IDEA prompts you to start a 30-day Ultimate trial, you can click Continue or dismiss the dialog. The free core features are all you need for this project.

Don't see the Welcome screen?

Make sure you fully completed the installation wizard. If IntelliJ IDEA opens but shows an error about missing components, restart your machine and try again.

Need more help? Help me get IntelliJ IDEA working.

Your development environment is ready to go. Next up, you will generate a brand new Spring Boot project and run it for the first time.

Generate Your Spring Boot Project

Your development environment is ready with Java and an IDE. Now it's time to generate the actual Spring Boot project you'll build your API in.

Spring Initializr is a web tool that generates pre-configured Spring Boot projects. You fill out a form, download a starter package, and have a running application in minutes.

In this step, get ready to:
  • Configure and download a Spring Boot project from Spring Initializr.
  • Open the project in your IDE.
  • Run the application and verify it starts successfully.
Configure your project on Spring Initializr

Spring Initializr lets you choose your build tool, language, dependencies, and project metadata from a single page.

  • Open your web browser and navigate to https://start.spring.io.
  • Confirm that Maven is selected under Project (it should be selected by default).
  • Confirm that Java is selected under Language (it should be selected by default).
  • Under Spring Boot, select a version starting with 3.5 (for example, 3.5.14). Do not select a SNAPSHOT or milestone version.
  • In the Group field, leave the default value com.example.
  • In the Artifact field, type book-catalog.
  • Click ADD DEPENDENCIES (or press Ctrl+B).
  • Type web in the search box that appears.
  • Select Spring Web from the results list.
  • Click the GENERATE button to download your project as a ZIP file.

What is Spring Initializr doing?

Spring Initializr generates a complete project structure including a Maven build file (pom.xml), a main application class, an application.properties file, and a Maven Wrapper so you can build without installing Maven separately.

The Spring Web dependency (spring-boot-starter-web) gives you an embedded Tomcat web server, Spring MVC for handling HTTP requests, and Jackson for automatic JSON conversion.

Extract and open the project in your IDE
  • Find the downloaded book-catalog.zip file in your Downloads folder.
  • Extract (unzip) the file to your Desktop. This creates a book-catalog folder.
  • Switch back to your IDE (IntelliJ IDEA or VS Code).
  • Click File in the top menu bar.
  • Click Open (IntelliJ) or Open Folder (VS Code).
  • Navigate to your Desktop and select the book-catalog folder, then click Open.
  • If your IDE asks whether to trust the project, click Trust Project.

Your IDE will take a moment to import the Maven project and download dependencies. Wait for the progress bar (usually at the bottom) to finish before continuing.

What should I see?

In the file explorer sidebar, you should see the project structure including src/main/java/com/example/bookcatalog/ with a file called BookCatalogApplication.java. You should also see pom.xml at the project root.

Don't see the project files?

Make sure you opened the book-catalog folder itself (the one containing pom.xml), not a parent folder or the ZIP file.

If IntelliJ shows a popup asking to import as a Maven project, click Import.

Help me find my project files in my IDE.

Run the application

The generated project includes a Maven Wrapper script that lets you build and run the project without installing Maven globally.

  • Open a terminal in your IDE. In IntelliJ, click View then Tool Windows then Terminal. In VS Code, press Ctrl+` (backtick).
  • Start the application by running the command for your operating system:

macOS

  • Run this command in your terminal:
./mvnw spring-boot:run

Permission denied?

If you see a permission error, make the wrapper executable first by running:

chmod +x mvnw

Then run ./mvnw spring-boot:run again.

Windows

  • Run this command in your terminal:
.\mvnw.cmd spring-boot:run

The first run downloads project dependencies, so it may take a minute or two.

What should I see in the terminal?

When the application starts successfully, you will see output confirming that Tomcat started on port 8080 and a line saying the application has started in a few seconds.

  • Open your browser and navigate to http://localhost:8080.

What should I see in the browser?

You will see a "Whitelabel Error Page" with a 404 status. This is expected. It means Spring Boot is running and listening for requests, but you have not defined any endpoints yet.

Seeing a port already in use error?

Another application is using port 8080. Close any other servers or applications running on that port, then try the command again.

If you see a message about JAVA_HOME not being set, make sure your JDK is installed correctly and the JAVA_HOME environment variable points to your JDK installation folder.

Help me fix my Spring Boot startup error.

✔️ Awesome, I've got everything!

Great. Double check you see BookCatalogApplication.java in your project and the app started on port 8080.

ⓧ I'd like to double check the full code

Here is what your generated BookCatalogApplication.java should look like:

package com.example.bookcatalog;

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

@SpringBootApplication
public class BookCatalogApplication {

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

}

And your application.properties file should contain:

spring.application.name=book-catalog

What is @SpringBootApplication?

The @SpringBootApplication annotation is a convenience shortcut that combines three annotations: @Configuration, @EnableAutoConfiguration, and @ComponentScan.

It tells Spring Boot to auto-configure itself based on the dependencies you added and to scan for components in the current package. The SpringApplication.run() method bootstraps the application and starts the embedded Tomcat web server.

Your Spring Boot application is running and ready to accept requests. Next up, you'll create the Book model class that defines the data structure your API will serve.

Create the Book Model

Your Spring Boot application is running successfully on port 8080. Now it needs to know what a "book" actually looks like as data.

Every REST API needs a data structure that defines the shape of the things it manages. You'll create a Java class called Book that represents one book in your catalog. This class becomes the blueprint that your API uses to send and receive book data as JSON.

In this step, get ready to:
  • Create a Book.java model class with four fields.
  • Add constructors and accessor methods to the class.
  • Learn how Spring Boot automatically converts the Book object to JSON.
Create the Book class with its fields

A model class in Java is a plain object that holds data. Your Book model defines four properties: a unique ID, a title, an author, and the year it was published.

  • In IntelliJ IDEA, right-click on the com.example.bookcatalog package in the Project tool window (left sidebar).
  • Select New, then Java Class.
  • Type Book as the class name and press Enter.
  • Replace the generated contents of the file with the following code:
package com.example.bookcatalog;

public class Book {

    // Each book has a unique ID, title, author, and publication year
    private Long id;
    private String title;
    private String author;
    private int year;
}

What does this code do?

The private keyword means these fields can only be accessed through methods you define (getters and setters). This is called encapsulation.

Using Long (uppercase) instead of long for the ID allows it to be null, which is useful when creating a new book before an ID is assigned.

You won't see a change in the browser yet. The next chunks add the methods that make this class usable by your API.

Add constructors, getters, and setters

Your class needs constructors to create Book objects, and getter/setter methods so that other parts of your application can read and update the fields.

  • In Book.java, place your cursor after the private int year; line.
  • Add the following two constructors below the fields:
    public Book() {
    }

    public Book(Long id, String title, String author, int year) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.year = year;
    }

Why two constructors?

The empty Book() constructor (called a no-arg constructor) is required by Jackson to create Book objects from incoming JSON data. Without it, deserialization fails.

The parameterized constructor lets you create a Book with all four values in a single line of code, which you'll use later to pre-load sample data.

You won't see a change yet. One more set of methods to add.

  • Add the following getters and setters below the constructors, before the closing } of the class:
    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 int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

What do getters and setters do?

Getters (like getTitle()) let other classes read a field's value. Setters (like setTitle()) let other classes update it.

Jackson uses these methods to convert your Book object into JSON (calling getters) and to build a Book from JSON in a request body (calling setters).

  • Save Book.java.
  • Re-run your application with .\.mvnw.cmd spring-boot:run in the terminal. You should see the application start without any errors, confirming your class compiles correctly.

Seeing a compilation error?

  • Check that Book.java is in the same package as BookCatalogApplication.java. The file path should be src/main/java/com/example/bookcatalog/Book.java.
  • Verify the first line reads package com.example.bookcatalog; exactly.
  • Make sure every opening brace { has a matching closing brace }.

Still stuck? Help me fix a compilation error in my Book.java model class.

✔️ Awesome, I've got everything!

Great. Double check you've saved Book.java and your application restarts without errors.

ⓧ I'd like to double check the full code

Here is the complete Book.java file:

package com.example.bookcatalog;

public class Book {

    private Long id;
    private String title;
    private String author;
    private int year;

    public Book() {
    }

    public Book(Long id, String title, String author, int year) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.year = year;
    }

    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 int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }
}
Understand how Jackson converts your class to JSON

What is Jackson?

Jackson is a JSON library that Spring Boot includes automatically when you add the spring-boot-starter-web dependency. It handles all the conversion between Java objects and JSON without any extra configuration.

When your API returns a Book object, Jackson calls each getter (getId(), getTitle(), etc.) and builds a JSON response like:

{
    "id": 1,
    "title": "1984",
    "author": "George Orwell",
    "year": 1949
}

When your API receives JSON in a request, Jackson calls the no-arg constructor, then uses the setters to populate each field. This is why both constructors and getters/setters are essential.

Your data model is ready. Next up, you'll build the controller that uses this Book class to handle HTTP requests and return real data from your API.

Build the REST Controller

Your Book model defines the shape of the data. Now you need a way for the outside world to actually request that data over HTTP.

In Spring Boot, a controller is the class that receives incoming HTTP requests and decides what to send back. You will create a controller that listens for GET requests and returns book data as JSON.

In this step, get ready to:
  • Create a controller class annotated with @RestController.
  • Implement GET endpoints that return all books and a single book by ID.
  • Pre-load sample books so the API has data to return immediately.
Create the BookController class

Every REST API needs a controller to handle incoming requests. The @RestController annotation tells Spring that this class handles web requests and returns data directly (not an HTML page).

  • In your IDE, create a new file called BookController.java in the same folder as your Book.java file (src/main/java/com/example/bookcatalog/).
  • Add the following code to set up the controller class with its annotations and fields:
package com.example.bookcatalog;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// Marks this class as a REST controller that handles HTTP requests
@RestController
// All endpoints in this class start with /books
@RequestMapping("/books")
public class BookController {

    // In-memory list to store books (no database needed)
    private final List<Book> books = new ArrayList<>();
    // Counter to generate unique IDs for each book
    private final AtomicLong idCounter = new AtomicLong();
}

What does this code do?

The @RestController annotation marks this class as a controller where every method returns data directly as JSON. It combines @Controller and @ResponseBody into one annotation.

The @RequestMapping("/books") annotation sets a base URL path. Every endpoint in this controller will start with /books.

The ArrayList stores books in memory. The AtomicLong generates unique IDs safely. We import extra annotations (like @PostMapping) because we will use them in the next step.

  • Save BookController.java.

Seeing red underlines in your IDE?

Make sure the file is in the exact same package folder as Book.java (src/main/java/com/example/bookcatalog/). If the package declaration at the top does not match the folder structure, Spring cannot find the class.

Help me fix red underlines in my BookController.java file.

Implement GET /books and GET /books/{id}

With the controller shell in place, you can now add methods that respond to GET requests. One method returns all books and the other returns a single book by its ID.

  • In BookController.java, add the following two methods inside the BookController class, below the idCounter field:
    // Returns the full list of books as JSON
    @GetMapping
    public List<Book> getAllBooks() {
        return books;
    }

    // Returns a single book matching the given ID, or null if not found
    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return books.stream()
                .filter(book -> book.getId().equals(id))
                .findFirst()
                .orElse(null);
    }

What does this code do?

The @GetMapping annotation maps HTTP GET requests to the method. Because the class already has @RequestMapping("/books"), the getAllBooks() method responds to GET /books.

The @GetMapping("/{id}") creates a path like /books/1. The @PathVariable annotation pulls the id value from the URL and passes it into the method.

The .stream().filter().findFirst().orElse(null) chain searches the list for a matching book. If no book has that ID, it returns null.

  • Save BookController.java.
  • Stop your running application (press Ctrl+C in the terminal), then restart it by running:
.\mvnw.cmd spring-boot:run
  • Open your browser and navigate to http://localhost:8080/books. You should see an empty JSON array: [].

Why is the list empty?

The controller has a books list but nothing has been added to it yet. The next substep adds sample data so you will see real books in the response.

Seeing a Whitelabel Error Page?

Double check that @RestController and @RequestMapping("/books") are directly above the class declaration. Also confirm the file is in the com.example.bookcatalog package so Spring's component scan finds it.

Help me fix a Whitelabel Error Page when visiting /books.

Add pre-loaded sample books

An empty list is not very exciting to test with. You will add a constructor that pre-loads three sample books so the API returns data immediately on startup.

  • In BookController.java, add the following constructor between the idCounter field and the getAllBooks() method:
    // Pre-load sample books when the controller is created
    public BookController() {
        books.add(new Book(idCounter.incrementAndGet(), "1984", "George Orwell", 1949));
        books.add(new Book(idCounter.incrementAndGet(), "To Kill a Mockingbird", "Harper Lee", 1960));
        books.add(new Book(idCounter.incrementAndGet(), "The Great Gatsby", "F. Scott Fitzgerald", 1925));
    }

What does this code do?

The constructor runs automatically when Spring creates the controller. It adds three books to the in-memory list with unique IDs generated by idCounter.incrementAndGet().

This means your API has data available the moment the application starts. No database setup required.

  • Save BookController.java.
  • Stop the application (Ctrl+C) and restart it by running:
.\mvnw.cmd spring-boot:run
  • Refresh http://localhost:8080/books in your browser. You should see a JSON array with three books including their IDs, titles, authors, and years.
  • Try http://localhost:8080/books/1 to see just the first book ("1984" by George Orwell).

Still seeing an empty list?

Make sure you restarted the application after saving. In-memory data is created fresh each time the app starts. If you only saved without restarting, the old version (without the constructor) is still running.

My API still returns an empty list after adding sample books.

How does Jackson convert this to JSON?

Spring Boot includes Jackson by default through the spring-boot-starter-web dependency. Jackson looks at your Book class getters (getId(), getTitle(), etc.) and automatically converts each Book object into a JSON object. No extra configuration needed.

✔️ Awesome, I've got everything!

Great. Double check you have saved BookController.java and that your browser shows three books at http://localhost:8080/books.

ⓧ I'd like to double check the full code

Here is the complete BookController.java at this point:

package com.example.bookcatalog;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/books")
public class BookController {

    private final List<Book> books = new ArrayList<>();
    private final AtomicLong idCounter = new AtomicLong();

    public BookController() {
        books.add(new Book(idCounter.incrementAndGet(), "1984", "George Orwell", 1949));
        books.add(new Book(idCounter.incrementAndGet(), "To Kill a Mockingbird", "Harper Lee", 1960));
        books.add(new Book(idCounter.incrementAndGet(), "The Great Gatsby", "F. Scott Fitzgerald", 1925));
    }

    @GetMapping
    public List<Book> getAllBooks() {
        return books;
    }

    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return books.stream()
                .filter(book -> book.getId().equals(id))
                .findFirst()
                .orElse(null);
    }
}

Your API can now read books. Next up, you will add endpoints to create, update, and delete books to complete the full set of CRUD operations.

Add Create, Update, and Delete Endpoints

Your API can read books, but a real catalog needs to let users add new books, update existing ones, and remove books they no longer want. Right now your API is read-only.

In this step, you will add three new endpoints that complete the full CRUD lifecycle. Each endpoint maps to a different HTTP method: POST for creating, PUT for updating, and DELETE for removing.

In this step, get ready to:
  • Add a POST endpoint that creates a new book.
  • Add a PUT endpoint that updates an existing book.
  • Add a DELETE endpoint that removes a book.
Add the POST endpoint to create books

When a client sends a POST request, they include a JSON body describing the new book. Spring Boot needs to convert that JSON into a Java object automatically.

  • In BookController.java, find the closing brace of the getBookById method.
  • Add the following method below it:
    @PostMapping
    public Book createBook(@RequestBody Book book) {
        // Assign a unique ID to the new book
        book.setId(idCounter.incrementAndGet());
        // Add the book to our in-memory list
        books.add(book);
        // Return the created book (now with its ID) as JSON
        return book;
    }

What does this code do?

The @PostMapping annotation tells Spring to route HTTP POST requests to this method.

The @RequestBody annotation tells Spring to take the JSON from the request body and convert it into a Book object using Jackson. The method then assigns a new ID and adds the book to the list.

  • Save BookController.java.

Seeing a red underline on @PostMapping?

The import for PostMapping and RequestBody should already exist at the top of your file from the previous step. If you see an error, check that your import section includes import org.springframework.web.bind.annotation.PostMapping; and import org.springframework.web.bind.annotation.RequestBody;.

Help me fix my import errors in BookController.java

Add the PUT endpoint to update books

Updating a book means finding it by ID in the list, then replacing it with the new data the client sends. If the book does not exist, the method returns null.

  • In BookController.java, add the following method below the createBook method you just wrote:
    @PutMapping("/{id}")
    public Book updateBook(@PathVariable Long id, @RequestBody Book book) {
        // Loop through the list to find the book with the matching ID
        for (int i = 0; i < books.size(); i++) {
            if (books.get(i).getId().equals(id)) {
                // Preserve the original ID and replace the book data
                book.setId(id);
                books.set(i, book);
                return book;
            }
        }
        // Return null if no book was found with that ID
        return null;
    }

What does this code do?

The @PutMapping("/{id}") annotation routes HTTP PUT requests that include an ID in the URL path.

The @PathVariable annotation extracts the id value from the URL, and @RequestBody converts the JSON body into a Book object. The method loops through the list, finds the matching book, and replaces it.

  • Save BookController.java.

Seeing a compilation error?

Make sure the method is placed inside the BookController class (before the final closing brace }). Also check that you have both the @PathVariable and @RequestBody annotations on the method parameters.

Help me debug my updateBook method

Add the DELETE endpoint to remove books

The delete operation removes a book from the list entirely and returns a confirmation message as plain text.

  • In BookController.java, add the following method below the updateBook method:
    @DeleteMapping("/{id}")
    public String deleteBook(@PathVariable Long id) {
        // Remove any book whose ID matches the given ID
        books.removeIf(book -> book.getId().equals(id));
        // Return a confirmation message
        return "Book with id " + id + " deleted.";
    }

What does this code do?

The @DeleteMapping("/{id}") annotation routes HTTP DELETE requests to this method. The removeIf method uses a lambda expression to find and remove the book with the matching ID in one step.

Unlike the other endpoints that return a Book object, this method returns a String. Spring Boot sends it back as plain text to confirm the deletion.

  • Save BookController.java.
  • Restart your application by pressing Ctrl+C in the terminal to stop it, then running .\mvnw.cmd spring-boot:run again.
  • Open your browser and visit http://localhost:8080/books. You should still see the three sample books, confirming the app started without errors.

Application won't start?

If you see a compilation error in the terminal, check that all three new methods are inside the BookController class braces. A misplaced closing brace } is the most common cause.

Also verify that each method has the correct return type: Book for createBook and updateBook, and String for deleteBook.

Help me fix my Spring Boot startup error

✔️ Awesome, I've got everything!

Great. Double check you have saved BookController.java and that your application is running.

ⓧ I'd like to double check the full code

Here is the complete BookController.java file with all five endpoints:

package com.example.bookcatalog;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/books")
public class BookController {

    private final List<Book> books = new ArrayList<>();
    private final AtomicLong idCounter = new AtomicLong();

    public BookController() {
        books.add(new Book(idCounter.incrementAndGet(), "1984", "George Orwell", 1949));
        books.add(new Book(idCounter.incrementAndGet(), "To Kill a Mockingbird", "Harper Lee", 1960));
        books.add(new Book(idCounter.incrementAndGet(), "The Great Gatsby", "F. Scott Fitzgerald", 1925));
    }

    @GetMapping
    public List<Book> getAllBooks() {
        return books;
    }

    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return books.stream()
                .filter(book -> book.getId().equals(id))
                .findFirst()
                .orElse(null);
    }

    @PostMapping
    public Book createBook(@RequestBody Book book) {
        book.setId(idCounter.incrementAndGet());
        books.add(book);
        return book;
    }

    @PutMapping("/{id}")
    public Book updateBook(@PathVariable Long id, @RequestBody Book book) {
        for (int i = 0; i < books.size(); i++) {
            if (books.get(i).getId().equals(id)) {
                book.setId(id);
                books.set(i, book);
                return book;
            }
        }
        return null;
    }

    @DeleteMapping("/{id}")
    public String deleteBook(@PathVariable Long id) {
        books.removeIf(book -> book.getId().equals(id));
        return "Book with id " + id + " deleted.";
    }
}

Your API now supports the full CRUD lifecycle. Next up, you will test every endpoint to confirm they work together.

Test Your API

You have a fully built REST API with five endpoints. Now it is time to prove they all work.

In this step, you will send real HTTP requests to your running server and confirm that every operation responds with the correct data.

In this step, get ready to:
  • Test GET endpoints in your browser.
  • Test POST, PUT, and DELETE using PowerShell.
  • Verify the full CRUD lifecycle works end to end.
Test GET endpoints in your browser

The simplest way to call a GET endpoint is to visit the URL directly in your browser. Your browser sends a GET request automatically when you navigate to any address.

  • Make sure your Spring Boot application is still running. If you stopped it, restart it by running this command in your terminal from the book-catalog folder:
.\mvnw.cmd spring-boot:run
  • Open your web browser.
  • Type http://localhost:8080/books into the address bar and press Enter.

You should see a JSON array containing three books. It looks similar to this:

[{"id":1,"title":"1984","author":"George Orwell","year":1949},{"id":2,"title":"To Kill a Mockingbird","author":"Harper Lee","year":1960},{"id":3,"title":"The Great Gatsby","author":"F. Scott Fitzgerald","year":1925}]

What does this JSON response mean?

Your Spring Boot server received the browser's GET request, matched it to the getAllBooks() method in your controller, and returned the books list as JSON.

Jackson (included automatically by the Spring Web starter) converted each Book Java object into a JSON object using the getter methods you defined.

  • Now test the single-book endpoint. Change the URL to http://localhost:8080/books/1 and press Enter.

You should see a single JSON object for the book with ID 1:

{"id":1,"title":"1984","author":"George Orwell","year":1949}

Seeing a blank page or error?

  • Check that your application is still running in the terminal. Look for the Spring Boot startup message.
  • Make sure the URL uses port 8080 and the path starts with /books.
  • If you see a "Whitelabel Error Page", double-check that BookController.java is in the same package as BookCatalogApplication.java.

Why isn't my Spring Boot API returning any data in the browser?

Test POST, PUT, and DELETE with PowerShell

Browsers can only send GET requests through the address bar. To test POST, PUT, and DELETE you need a tool that lets you choose the HTTP method and send a request body.

PowerShell's Invoke-RestMethod command sends HTTP requests and automatically parses JSON responses into readable objects.

What is Invoke-RestMethod?

Invoke-RestMethod is a built-in PowerShell cmdlet that sends HTTP requests to REST APIs. Unlike a browser, it lets you specify any HTTP method (GET, POST, PUT, DELETE) and include a JSON body in the request.

When the server responds with JSON, PowerShell automatically deserializes it into a readable object you can see right in the terminal.

  • Open a new PowerShell window. Press the Windows key, type PowerShell, and press Enter.
  • Create a new book by running this command:
Invoke-RestMethod -Uri "http://localhost:8080/books" -Method Post -ContentType "application/json" -Body '{"title":"Brave New World","author":"Aldous Huxley","year":1932}'

You should see output showing the new book with an assigned ID of 4:

id     : 4
title  : Brave New World
author : Aldous Huxley
year   : 1932

What does this command do?

  • -Uri specifies the endpoint URL to send the request to.
  • -Method Post tells PowerShell to send a POST request instead of the default GET.
  • -ContentType "application/json" tells the server the request body is JSON formatted.
  • -Body contains the JSON data for the new book. Notice you do not include an id field because the server assigns one automatically.
  • Confirm the new book was added by fetching all books:
Invoke-RestMethod -Uri "http://localhost:8080/books" -Method Get

You should now see four books in the output, including "Brave New World" at the end.

Seeing a red error in PowerShell?

  • Make sure you are using straight double quotes " and not curly quotes. Copy the command directly if needed.
  • Check that the JSON body uses single quotes on the outside and double quotes inside, or escape the inner double quotes.
  • Verify your Spring Boot application is still running in the other terminal window.

Why is my PowerShell Invoke-RestMethod POST request failing?

Complete the full CRUD lifecycle

You have tested Create (POST) and Read (GET). Now test Update (PUT) and Delete (DELETE) to confirm the full lifecycle works.

  • Update book 4 by changing its title. Run this command:
Invoke-RestMethod -Uri "http://localhost:8080/books/4" -Method Put -ContentType "application/json" -Body '{"title":"Brave New World (Revised)","author":"Aldous Huxley","year":1932}'

You should see the updated book returned with the new title:

id     : 4
title  : Brave New World (Revised)
author : Aldous Huxley
year   : 1932

What is happening with the PUT request?

The PUT method replaces the entire book object at the given ID. Your controller finds the book with ID 4, replaces it with the new data from the request body, and returns the updated book.

This is different from PATCH (which updates only specific fields). Your API uses PUT, meaning the client must send all fields even if only one changed.

  • Delete book 4 by running this command:
Invoke-RestMethod -Uri "http://localhost:8080/books/4" -Method Delete

You should see the confirmation message:

Book with id 4 deleted.
  • Verify the book is gone by fetching all books one more time:
Invoke-RestMethod -Uri "http://localhost:8080/books" -Method Get

You should see only the original three books. Book 4 is no longer in the list.

Book not updating or deleting?

  • Make sure you are using the correct ID number in the URL. If you restarted the server between tests, IDs reset back to 1, 2, and 3.
  • For PUT requests, check that your JSON body includes all four fields (title, author, year). Missing fields will be set to null or 0.

Why isn't my PUT or DELETE request working on my Spring Boot API?

You have confirmed that every endpoint works. Your REST API successfully creates, reads, updates, and deletes books. Next up, take on the Secret Mission and add search functionality to your API.

Secret mission

Add a Search-by-Author Endpoint

Your API can create, read, update, and delete books. But what if a user wants to find all books by a specific author without scrolling through the entire catalog? Add a search endpoint that filters books by author name.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The Spring Boot application running on localhost:8080.
  • The book-catalog project folder on your Desktop.

Keep everything running

No action needed. Choose this if you're still actively building or want to keep experimenting with your API.

  • Your Spring Boot application continues running on port 8080 until you close the terminal or press Ctrl+C.
  • JDK and your IDE (IntelliJ IDEA or VS Code) remain installed on your machine for future Java projects.
  • The book-catalog folder stays on your Desktop.

Pause - I'll come back to this later

Stop the running application to free up your terminal. Your project files stay on disk so you can restart any time.

  • In the terminal where your Spring Boot app is running, press Ctrl+C to stop the server.
  • To restart later, open a terminal in your book-catalog folder and run .\mvnw.cmd spring-boot:run again.

Delete - I don't want to use this again

Remove all project files and stop the running application. This cannot be undone.

  • In the terminal where your Spring Boot app is running, press Ctrl+C to stop the server.
  • Delete the project folder by running the appropriate command for your operating system.

On macOS/Linux:

cd ~/Desktop
rm -rf book-catalog

On Windows (PowerShell):

cd ~/Desktop
Remove-Item -Recurse -Force book-catalog

Nice Work!

Nice Work!

You just built a fully functional REST API from scratch with Spring Boot. Your Book Catalog API handles creating, reading, updating, and deleting books, all running on your local machine.

You've learned how to:

  • Built a CRUD REST API with Spring Boot that handles GET, POST, PUT, and DELETE requests.
  • Created a Book model class that Jackson automatically serializes to and from JSON.
  • Tested your API endpoints using both a browser and PowerShell commands to verify the full CRUD lifecycle.
  • Added a search endpoint with query parameter filtering to find books by author name.

Ready to quiz yourself?