Build a Bookmarks REST API
Build a Spring Boot REST API with full CRUD operations for bookmarks.
Introduction
30 Second Summary
Every time you save a link in a read-later app, star a repo on GitHub, or pin a recipe for tonight's dinner, an API is doing the heavy lifting behind the scenes. These APIs receive your request, store the data, and send back a confirmation.
In this project, you will build a REST API with Spring Boot that lets you save, retrieve, update, and delete web bookmarks. You will learn how HTTP requests get routed to your code, how Spring wires classes together automatically, and how Java objects become JSON responses.
What You'll Build
A Bookmarks REST API that handles full CRUD operations for web bookmarks, responding with JSON and running on an embedded Tomcat server at localhost:8080.
By the end of this project, you'll have:
- A Spring Boot application with a REST controller exposing 5 endpoints for creating, reading, updating, and deleting bookmarks.
- A clean controller-service architecture demonstrating Spring Boot's dependency injection.
- Hands-on experience testing API endpoints from the command line with curl.
- Secret Mission: Add search and filtering so you can query bookmarks by tag.
Are there any prerequisites?
You should be comfortable with Java fundamentals: classes, methods, collections, and generics. No prior Spring Boot experience is needed.
Before We Start
Before We Start...
Generate and Open Your Spring Boot Project
Every Spring Boot application starts from a scaffold. Instead of creating folders, config files, and dependency declarations by hand, you use a web tool called Spring Initializr to generate the whole structure in seconds.
In this step you will generate a ready-to-run Spring Boot project, open it in VS Code, and confirm the embedded server starts on your machine.
In this step, get ready to:
- Generate a Spring Boot project on Spring Initializr.
- Extract and open the project in VS Code.
- Verify the application starts successfully on port 8080.
Generate a project on Spring Initializr
Spring Initializr is a web-based tool that builds a complete project skeleton for you. You pick your build tool, language, dependencies, and metadata. It hands you a ZIP file ready to unpack and run.
- Open your browser and navigate to https://start.spring.io.
- Under Project, select Gradle - Groovy.
- Under Language, make sure Java is selected.
- Under Spring Boot, select 4.1.0.
- In the Group field, enter com.example.
- In the Artifact field, enter bookmarks.
- In the Name field, enter bookmarks.
- In the Package name field, enter com.example.bookmarks.
- Set Packaging to Jar.
- Set Java to 17.
- Click ADD DEPENDENCIES (or press Ctrl+B).
- Search for Spring Web and select it from the list.
- Click GENERATE to download the ZIP file.
What is Spring Initializr?
Spring Initializr is an official tool maintained by the Spring team. It assembles the correct folder layout, Gradle build file, and starter dependencies so you can jump straight into writing code.
The Spring Web dependency gives your project everything it needs to handle HTTP requests and return JSON responses. Under the hood it pulls in an embedded Tomcat server and the Spring MVC framework.
Extract and open the project in VS Code
- Locate the downloaded bookmarks.zip file in your Downloads folder.
- Extract (unzip) the file to your Desktop.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to and select the bookmarks folder on your Desktop, then click Open.
You should see the project structure in the VS Code Explorer sidebar. Confirm these files and folders are present:
- build.gradle in the project root.
- src/main/java/com/example/bookmarks/BookmarksApplication.java.
- gradlew.bat (the Gradle wrapper script for Windows).
What is the Gradle wrapper?
The gradlew.bat file is a script that downloads and runs the correct version of Gradle for your project. You do not need Gradle installed globally. The wrapper ensures everyone on the team uses the same build tool version.
Don't see these files?
Make sure you opened the inner bookmarks folder, not the parent folder that contains it. The build.gradle file should be at the top level of whatever folder you opened.
If the ZIP extracted into a nested bookmarks/bookmarks structure, open the innermost folder.
Help me find the right folder to open in VS Code.
Run the app to verify it starts
Before writing any code, confirm the generated project boots up cleanly. This catches environment issues early.
- Open a terminal in VS Code by pressing Ctrl+` (backtick key, top-left of your keyboard).
- Start the application by running this command:
.\gradlew.bat bootRun
What does this command do?
The .\gradlew.bat part invokes the Gradle wrapper. The bootRun task compiles your code and launches the embedded Tomcat server. Your application becomes available at localhost:8080.
The first run downloads dependencies, so it may take a minute. You should see output ending with a line that says Started BookmarksApplication and confirms the app is listening on port 8080.
- Press Ctrl+C in the terminal to stop the application.
Don't see Started BookmarksApplication?
If you see a message about port 8080 already being in use, another process is occupying that port. Close any other local servers (Node, Python, Docker) and try again.
If you see a JAVA_HOME error, your JDK is not on the system PATH. Verify your JDK installation by running java -version in the terminal.
Help me fix my Spring Boot startup error.
✔️ Awesome, I've got everything!
Great. Double check you saw Started BookmarksApplication in your terminal output and then stopped it with Ctrl+C.
ⓧ I'd like to double check the full code
Here is what your build.gradle should look like:
plugins {
id 'java'
id 'org.springframework.boot' version '4.1.0'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
}
And here is BookmarksApplication.java:
package com.example.bookmarks;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BookmarksApplication {
public static void main(String[] args) {
SpringApplication.run(BookmarksApplication.class, args);
}
}
Your project skeleton is ready. Next up, you will create a Bookmark model class to represent the data your API will manage.
Create the Bookmark Model
Your Spring Boot project is set up and verified. Now you need a way to represent the data your API will manage.
A model class defines the shape of a bookmark in Java. Spring Boot uses this class to automatically convert Java objects into JSON responses when your API sends data back to a client.
In this step, get ready to:
- Create a Bookmark model class with fields, constructors, getters, and setters.
- Verify the project compiles cleanly with the new class.
Create the Bookmark class
Every bookmark your API manages needs four pieces of information: a unique ID, a title, a URL, and a tag for categorization.
- In the VS Code Explorer sidebar, expand src/main/java/com/example/bookmarks.
- Right-click on the bookmarks folder and select New File.
- Name the file Bookmark.java and press Enter.
- Paste the following code into Bookmark.java:
package com.example.bookmarks;
public class Bookmark {
private Long id;
private String title;
private String url;
private String tag;
// No-argument constructor (required for JSON deserialization)
public Bookmark() {
}
// Full constructor for creating bookmarks with all fields
public Bookmark(Long id, String title, String url, String tag) {
this.id = id;
this.title = title;
this.url = url;
this.tag = tag;
}
// Getters and setters for each field
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 getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
}
What does this code do?
This class is a plain Java object (POJO) that represents one bookmark. Each field maps to a property in the JSON your API sends and receives.
- id uniquely identifies each bookmark. It uses Long (the object wrapper) instead of primitive long so it can be null before the service assigns one.
- The no-argument constructor lets Jackson (the JSON library) create Bookmark objects from incoming JSON request bodies.
- Getters let Spring Boot read each field when serializing to JSON. Setters let Jackson write values when deserializing from JSON.
- Save Bookmark.java by pressing Cmd+S (macOS) or Ctrl+S (Windows).
Verify the project compiles
Running a build confirms that your new class has no syntax errors and fits into the existing project structure.
- Compile the project by running this command in the VS Code terminal:
.\gradlew.bat build
- You should see BUILD SUCCESSFUL in the terminal output.
Seeing a compilation error?
- Check that the file is named exactly Bookmark.java (capital B). Java requires the filename to match the class name.
- Make sure the file is inside src/main/java/com/example/bookmarks/. The package declaration must match the folder structure.
- Verify you saved the file before running the build command.
Why won't my Bookmark.java file compile?
✔️ Awesome, I've got everything!
Great. Double check you've saved your file.
ⓧ I'd like to double check the full code
Here is the complete Bookmark.java file for comparison:
package com.example.bookmarks;
public class Bookmark {
private Long id;
private String title;
private String url;
private String tag;
public Bookmark() {
}
public Bookmark(Long id, String title, String url, String tag) {
this.id = id;
this.title = title;
this.url = url;
this.tag = tag;
}
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 getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
}
Your data model is locked in. Next up, you'll build the service layer that manages a collection of these bookmarks in memory.
Build the Service Layer
Your Bookmark model defines what a bookmark looks like. Now you need code that manages a collection of bookmarks: adding them, finding them, updating them, and removing them.
In this step, you'll create a service class that handles all bookmark data operations. Spring Boot's @Service annotation marks this class so Spring automatically discovers it and wires it into other parts of your app.
In this step, get ready to:
- Create the BookmarkService class with @Service annotation and in-memory storage fields.
- Implement five CRUD methods for managing bookmarks.
- Verify the project compiles cleanly.
Create the BookmarkService class
The service layer pattern keeps your data-management logic separate from your HTTP-handling logic. This separation makes your code easier to test and maintain as the project grows.
- In the VS Code Explorer sidebar, navigate to src/main/java/com/example/bookmarks/ (the same folder where Bookmark.java lives).
- Right-click the bookmarks folder and select New File.
- Name the file BookmarkService.java and press Enter.
- Paste the following code into BookmarkService.java:
package com.example.bookmarks;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Service;
@Service
public class BookmarkService {
private final List<Bookmark> bookmarks = new ArrayList<>();
private final AtomicLong idCounter = new AtomicLong(1);
}
What does this code do?
- @Service tells Spring Boot to register this class as a bean. Other classes can then receive it automatically through dependency injection.
- bookmarks is an ArrayList that stores bookmark objects in memory. This is a simple substitute for a database.
- idCounter is an AtomicLong starting at 1. Each time you create a bookmark, it generates a unique ID and increments.
You won't see a visible change yet. The next substep adds the methods that use these fields.
Add the CRUD methods
CRUD stands for Create, Read, Update, and Delete. These are the four basic operations every data-driven application needs. You'll implement five methods that cover all four operations.
- In BookmarkService.java, place your cursor on the blank line just above the closing } brace of the class.
- Add the read methods by pasting this code:
public List<Bookmark> getAllBookmarks() {
return bookmarks;
}
public Optional<Bookmark> getBookmarkById(Long id) {
return bookmarks.stream()
.filter(b -> b.getId().equals(id))
.findFirst();
}
What do these methods do?
- getAllBookmarks() returns the entire list. The controller will call this when a user requests all bookmarks.
- getBookmarkById() uses Java streams to search through the list for a bookmark matching the given ID. It returns an Optional because the bookmark might not exist.
You won't see a change yet. Next you'll add the write operations.
- Below the getBookmarkById method (but still inside the class), add the write methods by pasting this code:
public Bookmark createBookmark(Bookmark bookmark) {
bookmark.setId(idCounter.getAndIncrement());
bookmarks.add(bookmark);
return bookmark;
}
public Optional<Bookmark> updateBookmark(Long id, Bookmark updated) {
return getBookmarkById(id).map(existing -> {
existing.setTitle(updated.getTitle());
existing.setUrl(updated.getUrl());
existing.setTag(updated.getTag());
return existing;
});
}
public boolean deleteBookmark(Long id) {
return bookmarks.removeIf(b -> b.getId().equals(id));
}
What do these methods do?
- createBookmark() assigns the next available ID to the bookmark, adds it to the list, and returns it.
- updateBookmark() looks up the existing bookmark by ID. If found, it overwrites the title, URL, and tag with the new values. It returns an Optional so the caller knows whether the update succeeded.
- deleteBookmark() removes any bookmark whose ID matches. It returns true if a bookmark was removed, or false if no match was found.
- Save BookmarkService.java.
You'll confirm everything compiles in the next substep.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
package com.example.bookmarks;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Service;
@Service
public class BookmarkService {
private final List<Bookmark> bookmarks = new ArrayList<>();
private final AtomicLong idCounter = new AtomicLong(1);
public List<Bookmark> getAllBookmarks() {
return bookmarks;
}
public Optional<Bookmark> getBookmarkById(Long id) {
return bookmarks.stream()
.filter(b -> b.getId().equals(id))
.findFirst();
}
public Bookmark createBookmark(Bookmark bookmark) {
bookmark.setId(idCounter.getAndIncrement());
bookmarks.add(bookmark);
return bookmark;
}
public Optional<Bookmark> updateBookmark(Long id, Bookmark updated) {
return getBookmarkById(id).map(existing -> {
existing.setTitle(updated.getTitle());
existing.setUrl(updated.getUrl());
existing.setTag(updated.getTag());
return existing;
});
}
public boolean deleteBookmark(Long id) {
return bookmarks.removeIf(b -> b.getId().equals(id));
}
}
Confirm the project compiles
- Run the build command in your VS Code terminal:
.\gradlew.bat build
- You should see BUILD SUCCESSFUL in the terminal output.
Seeing a build error?
- Check that BookmarkService.java is saved in src/main/java/com/example/bookmarks/. A wrong folder breaks the package declaration.
- Make sure the closing } brace of the class is present after your last method. A missing brace causes a compilation error.
- Verify the package com.example.bookmarks; line at the top matches the folder path exactly.
Help me fix my Gradle build error for BookmarkService.
Your service layer is ready to manage bookmarks. Next up, you'll create the REST controller that connects HTTP requests to these service methods.
Create the REST Controller
Your service layer is compiled and ready to manage bookmarks in memory. Now you need a way for the outside world to talk to that service over HTTP.
A REST controller is the class that receives incoming HTTP requests and routes them to the correct service method. Spring Boot handles all the plumbing (parsing JSON, mapping URLs, returning responses) so your controller stays thin and focused.
In this step, get ready to:
- Create a REST controller annotated with @RestController and @RequestMapping.
- Wire up five endpoint methods covering full CRUD operations.
- Start the app and confirm it runs without errors.
Create the controller class with constructor injection
The controller needs two annotations to work as a REST endpoint. @RestController tells Spring this class handles HTTP requests and returns JSON. @RequestMapping("/api/bookmarks") sets the base URL path for every endpoint inside the class.
- In the Explorer sidebar in VS Code, right-click the src/main/java/com/example/bookmarks folder and select New File.
- Name the file BookmarkController.java.
- Paste the following code into the new file:
package com.example.bookmarks;
import java.util.List;
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 returns JSON automatically
@RestController
// All endpoints in this class start with /api/bookmarks
@RequestMapping("/api/bookmarks")
public class BookmarkController {
// Spring injects this dependency via the constructor below
private final BookmarkService bookmarkService;
// Constructor injection - Spring provides the service instance automatically
public BookmarkController(BookmarkService bookmarkService) {
this.bookmarkService = bookmarkService;
}
}
What does this code do?
@RestController combines two annotations: @Controller (handles requests) and @ResponseBody (converts return values to JSON). Together they mean any object you return from a method gets serialized to JSON automatically.
The constructor accepts a BookmarkService parameter. Because BookmarkService is annotated with @Service, Spring knows how to create one and pass it in. This is dependency injection in action.
- Save BookmarkController.java.
- You won't see a visible change yet. The next substep adds the endpoint methods that make this controller functional.
Seeing red underlines in the editor?
Make sure the file is inside src/main/java/com/example/bookmarks/ (the same package as BookmarkService.java). If the package line at the top doesn't match the folder structure, Java can't find related classes.
help me fix red underlines in BookmarkController.java
Add the endpoint methods
Each endpoint method maps to an HTTP verb. @GetMapping handles GET requests, @PostMapping handles POST, and so on. The methods simply delegate to BookmarkService and return the result.
- In BookmarkController.java, place your cursor after the closing brace of the constructor (but before the final closing brace of the class).
- Add the following five methods:
// GET /api/bookmarks - returns all bookmarks as a JSON list
@GetMapping
public List<Bookmark> getAllBookmarks() {
return bookmarkService.getAllBookmarks();
}
// GET /api/bookmarks/{id} - returns one bookmark by its ID
@GetMapping("/{id}")
public Bookmark getBookmarkById(@PathVariable Long id) {
return bookmarkService.getBookmarkById(id).orElse(null);
}
// POST /api/bookmarks - creates a new bookmark from the JSON request body
@PostMapping
public Bookmark createBookmark(@RequestBody Bookmark bookmark) {
return bookmarkService.createBookmark(bookmark);
}
// PUT /api/bookmarks/{id} - updates an existing bookmark
@PutMapping("/{id}")
public Bookmark updateBookmark(@PathVariable Long id, @RequestBody Bookmark bookmark) {
return bookmarkService.updateBookmark(id, bookmark).orElse(null);
}
// DELETE /api/bookmarks/{id} - removes a bookmark
@DeleteMapping("/{id}")
public void deleteBookmark(@PathVariable Long id) {
bookmarkService.deleteBookmark(id);
}
What do these annotations do?
- @PathVariable extracts the {id} portion from the URL and passes it as a method parameter.
- @RequestBody tells Spring to parse the incoming JSON body into a Bookmark object automatically.
- .orElse(null) returns null if no bookmark is found, which Spring converts to an empty response.
- Save BookmarkController.java.
- You won't see a visible change yet. The next substep starts the server so you can confirm everything compiles.
Red underlines on the annotation imports?
Check that all the import statements from the first code block are still at the top of your file. Each annotation (@GetMapping, @PostMapping, etc.) needs its own import line.
help me fix import errors in my Spring Boot controller
✔️ Awesome, I've got everything!
Great. Make sure you've saved the file before moving on.
ⓧ I'd like to double check the full code
package com.example.bookmarks;
import java.util.List;
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("/api/bookmarks")
public class BookmarkController {
private final BookmarkService bookmarkService;
public BookmarkController(BookmarkService bookmarkService) {
this.bookmarkService = bookmarkService;
}
@GetMapping
public List<Bookmark> getAllBookmarks() {
return bookmarkService.getAllBookmarks();
}
@GetMapping("/{id}")
public Bookmark getBookmarkById(@PathVariable Long id) {
return bookmarkService.getBookmarkById(id).orElse(null);
}
@PostMapping
public Bookmark createBookmark(@RequestBody Bookmark bookmark) {
return bookmarkService.createBookmark(bookmark);
}
@PutMapping("/{id}")
public Bookmark updateBookmark(@PathVariable Long id, @RequestBody Bookmark bookmark) {
return bookmarkService.updateBookmark(id, bookmark).orElse(null);
}
@DeleteMapping("/{id}")
public void deleteBookmark(@PathVariable Long id) {
bookmarkService.deleteBookmark(id);
}
}
Start the app and confirm it runs
Running the application is the true test of whether your controller, service, and model are all wired together correctly. If Spring can't inject the service or finds a broken annotation, it will fail to start.
- Start the app by running this command in your VS Code terminal:
.\gradlew.bat bootRun
- Wait for the console output to settle. You should see a line containing Started BookmarksApplication near the bottom.
- The app is now listening on port 8080. Leave it running for the next step.
What does this prove?
A successful startup means Spring found your @RestController, resolved the BookmarkService dependency, and mapped all five endpoints to their URLs. If any annotation was misspelled or an import was missing, you'd see an error stack trace instead.
Seeing an error on startup?
If you see a stack trace mentioning BeanCreationException, check that BookmarkService.java has the @Service annotation. Without it, Spring doesn't know how to create the service for injection.
If you see a port conflict (Address already in use), another process is using port 8080. Stop any other running applications and try again.
help me fix my Spring Boot app startup error
Your API is live and listening for requests. Next up, you'll send real HTTP requests to each endpoint and see your bookmarks come back as JSON.
Test Your API
Your REST controller is wired up and your app is running. Now it's time to see the API in action by sending real HTTP requests and inspecting the JSON responses.
You'll use curl from the command line to talk directly to your API. This is how developers test APIs before building a frontend.
In this step, get ready to:
- Create a bookmark by sending a POST request.
- Test GET, PUT, and DELETE endpoints.
- Confirm multiple bookmarks appear together in the list.
Create your first bookmark
Your Spring Boot app is already running in one terminal from the previous step. You need a separate terminal to send requests to it.
- In VS Code, open a new terminal by clicking **Terminal** in the top menu bar, then **New Terminal**.
- This opens a fresh PowerShell session while your app keeps running in the other terminal tab.
Why a second terminal?
Your first terminal is busy running the Spring Boot server. If you type commands there, you'd have to stop the app first. A second terminal lets you send requests while the server stays live.
- Create your first bookmark by running this command:
curl.exe -X POST http://localhost:8080/api/bookmarks -H "Content-Type: application/json" -d "{\"title\":\"Spring Docs\",\"url\":\"https://spring.io\",\"tag\":\"java\"}"
What does this command do?
- curl.exe sends an HTTP request from your terminal. You use curl.exe (not just curl) because PowerShell has a built-in alias called curl that points to a different tool.
- -X POST sets the HTTP method to POST, which means "create something new."
- -H "Content-Type: application/json" tells the server you're sending JSON data.
- -d attaches the JSON body containing your bookmark's title, URL, and tag.
- You should see a JSON response like this in your terminal:
{"id":1,"title":"Spring Docs","url":"https://spring.io","tag":"java"}
The id field confirms Spring Boot received your data, assigned it an ID, and sent the full bookmark back as JSON automatically.
Seeing 'connection refused' or no response?
- Make sure your app is still running in the other terminal tab. You should see "Started BookmarksApplication" in that tab's output.
- If the app stopped, switch to that terminal and run .\gradlew.bat bootRun again.
- Check that you're using curl.exe (with the .exe) and not just curl. PowerShell's curl alias behaves differently.
Help me debug my curl command in PowerShell
Test the remaining endpoints
A full REST API supports four operations: Create, Read, Update, and Delete. You just tested Create. Now try the other three.
- Retrieve all bookmarks by running:
curl.exe http://localhost:8080/api/bookmarks
- You should see a JSON array containing your Spring Docs bookmark.
- Retrieve a single bookmark by its ID by running:
curl.exe http://localhost:8080/api/bookmarks/1
- You should see just the one bookmark object (not wrapped in an array).
- Update the bookmark's title and URL by running:
curl.exe -X PUT http://localhost:8080/api/bookmarks/1 -H "Content-Type: application/json" -d "{\"title\":\"Spring Reference\",\"url\":\"https://docs.spring.io\",\"tag\":\"java\"}"
- The response shows the updated bookmark with the new title and URL, but the same id and tag.
- Delete the bookmark by running:
curl.exe -X DELETE http://localhost:8080/api/bookmarks/1
- Confirm the deletion worked by listing all bookmarks again:
curl.exe http://localhost:8080/api/bookmarks
- You should see an empty array [] confirming the bookmark was removed.
What are CRUD operations?
CRUD stands for Create, Read, Update, and Delete. These are the four basic operations every data-driven application needs. Each maps to an HTTP method: POST creates, GET reads, PUT updates, and DELETE removes.
Getting an empty response from PUT?
- If you already deleted the bookmark, there's nothing at ID 1 to update. Create a new bookmark first with POST, then try PUT using the new ID in the response.
- Make sure your JSON includes all three fields: title, url, and tag.
Help me troubleshoot my PUT or DELETE requests
Create a second bookmark and confirm both appear
A real bookmark collection has more than one entry. Let's add a second bookmark with a different tag to confirm the API handles multiple items.
- Create a fresh bookmark (to replace the one you deleted) by running:
curl.exe -X POST http://localhost:8080/api/bookmarks -H "Content-Type: application/json" -d "{\"title\":\"Spring Docs\",\"url\":\"https://spring.io\",\"tag\":\"java\"}"
- Create a second bookmark with a different tag by running:
curl.exe -X POST http://localhost:8080/api/bookmarks -H "Content-Type: application/json" -d "{\"title\":\"GitHub\",\"url\":\"https://github.com\",\"tag\":\"tools\"}"
- List all bookmarks to confirm both appear by running:
curl.exe http://localhost:8080/api/bookmarks
- You should see a JSON array with two bookmarks. One has the tag java and the other has tools.
Why do the IDs keep incrementing?
Even after deleting bookmark 1, the next bookmark gets ID 2 (or higher). The AtomicLong counter in your service never resets. This mirrors how real databases work. Deleted IDs are never reused.
Only seeing one bookmark in the list?
- Make sure you ran both POST commands. Each one creates a separate bookmark.
- If you restarted the app between commands, all in-memory data is lost. Restart and run both POST commands again.
Help me figure out why my bookmarks aren't showing up
Secret mission
Add Search by Tag
Your API can create, read, update, and delete bookmarks. But what if you have dozens of bookmarks and only want to see the ones tagged "java"? Add query parameter filtering so users can search bookmarks by tag.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your project files for future reference, or delete them to free up disk space.
Resources you used:
- The bookmarks project folder (contains all source code and Gradle wrapper files).
- The Spring Boot process running on port 8080.
Keep everything running
No action needed. Choose this if you plan to keep building on your Bookmarks API or want to reference the code later.
- Your project files stay in the bookmarks folder wherever you extracted the ZIP.
- If the Spring Boot app is still running in your terminal, you can leave it running or press Ctrl+C to stop it at any time.
Pause - I'll come back to this later
Stop the running process to free up the port, but keep your project files for later.
- Switch to the terminal where your Spring Boot app is running.
- Press Ctrl+C to stop the app.
- Your project files remain in the bookmarks folder. To restart later, open the folder in VS Code and run .\gradlew.bat bootRun again.
Delete - I don't want to use this again
Remove all project files and stop any running processes.
- If the Spring Boot app is still running, switch to that terminal and press Ctrl+C to stop it.
- Close the project folder in VS Code by clicking File, then Close Folder.
- Delete the bookmarks folder. On macOS/Linux, open a terminal and run:
rm -rf ~/Desktop/bookmarks
On Windows, open PowerShell and run:
Remove-Item -Recurse -Force ~\Desktop\bookmarks
- If you extracted the folder to a different location, navigate to that location and delete the bookmarks folder from there instead.
Nice Work!
Nice Work!
Nice work! You just built a fully functional REST API with Spring Boot 4.1.0 that handles bookmark management through proper HTTP endpoints.
You've learned how to:
- Built a REST controller with full CRUD operations (Create, Read, Update, Delete) using proper HTTP methods and JSON responses.
- Created a clean controller-service architecture using Spring Boot's dependency injection with constructor injection.
- Tested API endpoints from the command line with curl, sending requests and verifying JSON responses.
- Secret Mission: Added query parameter filtering with @RequestParam and Java streams to search bookmarks by tag.
Ready to quiz yourself?