A REST API is how one program talks to another over the web. When a mobile app loads your profile, or a website shows a list of products, there is almost always a REST API behind it, quietly handing over data. In this tutorial you will build one from scratch with Spring Boot, understand every line, and learn the patterns you will reuse in almost every project. We will go slowly and explain the why, not just the how.
What REST actually means
REST is a set of ideas for building web services. The core idea is simple: you have resources, such as users or products, and you act on them using standard web requests. Each resource has an address, called a URL, and you use different request types to read, create, update or delete it. You do not need to memorise the theory to get started; you mostly need to know four request types and what each is for.
- GET reads data. Asking for a list of products or a single product uses GET.
- POST creates something new, such as adding a product.
- PUT (or PATCH) updates something that already exists.
- DELETE removes something.
These four map neatly onto the things most apps need to do with their data. People often call this pattern CRUD, short for Create, Read, Update and Delete.
Setting up the project
Before writing an endpoint, you need a Spring Boot project with the web tools included. The quickest way is Spring Initializr at start.spring.io. Choose Maven, Java, and add the Spring Web dependency. Generate the project, unzip it, and open it in your editor. If you have followed the earlier setup tutorial, you already have everything you need.
The important dependency is spring-boot-starter-web. It brings in the web server and the tools that turn your Java objects into JSON automatically. You do not configure any of it; adding the starter is enough.
Your first endpoint
Let us start with the smallest possible working endpoint, then grow it. Create a new class called ProductController:
@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping
public String list() {
return "This will list products";
}
}
Run the app and open http://localhost:8080/products in your browser. You will see the text. Let us unpack the three annotations, because you will use them constantly.
@RestController
This tells Spring two things: the class handles web requests, and whatever its methods return should be sent straight back to the caller as the response body. Without the Rest part, Spring would think the returned text was the name of a web page to display.
@RequestMapping("/products")
This sets a base path for every method in the class. Because we put it on the class, every endpoint inside starts with /products. It saves you from repeating the path on each method and keeps related endpoints grouped.
@GetMapping
This marks the method as the handler for GET requests to the base path. Since the class path is /products and there is nothing extra here, this method answers GET /products.
Returning real data instead of text
An API that returns a sentence is not very useful. Real APIs return structured data. In Java you model that data as a class. Create a simple Product class:
public class Product {
private Long id;
private String name;
private double price;
public Product(Long id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
// getters and setters
}
Now change the controller to return a list of products. To keep this tutorial focused on the API itself, we will store the products in memory rather than a database:
@RestController
@RequestMapping("/products")
public class ProductController {
private final List products = new ArrayList<>(List.of(
new Product(1L, "Notebook", 4.50),
new Product(2L, "Pen", 1.20)
));
@GetMapping
public List list() {
return products;
}
}
Restart and open /products again. This time you will see JSON, something like this:
[
{ "id": 1, "name": "Notebook", "price": 4.5 },
{ "id": 2, "name": "Pen", "price": 1.2 }
]
You did not write any code to build that JSON. Spring Boot saw that your method returns a list of objects and converted it for you. This automatic conversion is one of the biggest conveniences of the framework.
Fetching a single item
Listing everything is common, but so is fetching one item by its id. The id belongs in the URL path, like /products/1. You capture it with a path variable:
@GetMapping("/{id}")
public Product getOne(@PathVariable Long id) {
return products.stream()
.filter(p -> p.getId().equals(id))
.findFirst()
.orElse(null);
}
The {id} in the path is a placeholder. The @PathVariable annotation tells Spring to take that part of the URL and pass it into your method. Now GET /products/1 returns just the notebook. We will improve the not-found case shortly.
Creating data with POST
To add a new product, the caller sends its details in the request body as JSON. You receive it with @RequestBody:
@PostMapping
public Product create(@RequestBody Product product) {
products.add(product);
return product;
}
The @RequestBody annotation is the mirror image of returning an object. Spring reads the incoming JSON and builds a Product object from it. You then store it and return it, often so the caller can see the final saved version, including any id the server assigned.
You cannot test POST by typing a URL in the browser, because browsers only send GET that way. Use a tool such as Postman, or the curl command in your terminal:
curl -X POST http://localhost:8080/products
-H "Content-Type: application/json"
-d '{"id":3,"name":"Eraser","price":0.9}'
Updating and deleting
Updating uses PUT and usually combines a path variable, to say which item, with a request body, to carry the new values:
@PutMapping("/{id}")
public Product update(@PathVariable Long id,
@RequestBody Product updated) {
// find the product and replace its values
return updated;
}
Deleting uses DELETE and only needs to know which item to remove:
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
products.removeIf(p -> p.getId().equals(id));
}
With these four request types in place, your API can now create, read, update and delete products. That is a complete CRUD API.
Returning proper status codes
Every web response carries a status code, a number that tells the caller what happened. You have probably seen 404, which means not found. A good API uses these codes correctly so callers can react. Returning an object directly always gives 200, which means success, even when the item was missing. To control the code, wrap your result in ResponseEntity:
@GetMapping("/{id}")
public ResponseEntity getOne(@PathVariable Long id) {
return products.stream()
.filter(p -> p.getId().equals(id))
.findFirst()
.map(ResponseEntity::ok) // 200 with the product
.orElse(ResponseEntity.notFound().build()); // 404
}
Now a missing product returns 404 instead of an empty 200 response. Here are the status codes you will use most:
- 200 OK: the request succeeded.
- 201 Created: a POST successfully created something.
- 400 Bad Request: the caller sent invalid data.
- 404 Not Found: the requested item does not exist.
- 500 Internal Server Error: something broke on the server.
A few good habits
As your API grows, a handful of habits keep it clean and predictable:
- Name resources with plural nouns, like /products, not /getProducts. The request type already says what you are doing.
- Keep controllers thin. They should receive the request and hand the real work to a service class. This keeps your logic testable and your controllers readable.
- Validate incoming data. Never trust that the request body is complete or sensible.
- Return consistent error responses so callers always know what to expect.
Where to go next
You now have a working REST API and understand what each part does. The natural next step is to store your data in a real database instead of an in-memory list, so it survives restarts. The tutorial on connecting Spring Boot to a database with JPA picks up exactly where this one leaves off. After that, adding validation and centralised error handling will take your API from working to production-ready.
The pattern you learned here, a controller with GET, POST, PUT and DELETE methods over a resource, is the backbone of most Spring Boot services you will ever build. Once it feels natural, the rest of the framework becomes much easier to learn.