Java tutorials > Frameworks and Libraries > Specific Frameworks (Spring, Hibernate) > How to create RESTful APIs using Spring Boot?

How to create RESTful APIs using Spring Boot?

This tutorial demonstrates how to create RESTful APIs using Spring Boot. We'll cover the essential annotations, project setup, and best practices for building robust and maintainable APIs.

Project Setup with Spring Initializr

The easiest way to start a Spring Boot project is using Spring Initializr (start.spring.io). Follow these steps:

  1. Go to start.spring.io.
  2. Choose your project details:
    • Project: Gradle or Maven (choose your preference)
    • Language: Java
    • Spring Boot: Choose a stable version (e.g., 3.2.x)
    • Group: Your organization's domain (e.g., com.example)
    • Artifact: Project name (e.g., rest-api)
    • Name: Project name (e.g., rest-api)
    • Description: A brief description of your project.
    • Package name: Your package name (e.g., com.example.restapi)
    • Packaging: Jar
    • Java: 17 or later
  3. Add dependencies: Choose 'Spring Web' (for building web applications, including RESTful APIs). Optionally, add 'Lombok' to reduce boilerplate code. You can add other dependencies like Spring Data JPA for database access as required.
  4. Click 'GENERATE'. This will download a zip file containing your initial project structure.
  5. Extract the zip file and import the project into your IDE (IntelliJ IDEA, Eclipse, or VS Code).

Creating a Simple REST Controller

Here's a basic REST controller example:

  1. @RestController: This annotation marks the class as a REST controller. It combines @Controller and @ResponseBody, meaning methods in this class will return data directly in the response body.
  2. @GetMapping("/hello"): This annotation maps HTTP GET requests with the path "/hello" to the hello() method.
  3. @GetMapping("/hello/{name}"): This maps GET requests with a path variable 'name' to the helloName() method.
  4. @PathVariable String name: This annotation extracts the value of the 'name' path variable from the URL and passes it as an argument to the method.

package com.example.restapi.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }

    @GetMapping("/hello/{name}")
    public String helloName(@PathVariable String name) {
        return "Hello, " + name + "!";
    }
}

Running the Application

To run the Spring Boot application, simply execute the main method in the class generated by Spring Initializr (usually located in src/main/java/com/example/restapi/RestApiApplication.java or a similar path). Alternatively, use your IDE's run configuration or the command line (./mvnw spring-boot:run for Maven, ./gradlew bootRun for Gradle).

Once the application is running, you can access the endpoints:

  • http://localhost:8080/hello (returns "Hello, World!")
  • http://localhost:8080/hello/YourName (returns "Hello, YourName!")

Creating a Resource Representation (Data Model)

A resource representation defines the structure of the data your API will expose. For example, a Product class:

This simple class defines the attributes of a product. We use it to represent data transferred via our REST API.

package com.example.restapi.model;

public class Product {
    private Long id;
    private String name;
    private String description;
    private double price;

    // Constructors, Getters, and Setters (using Lombok for brevity)

    public Product() {}

    public Product(Long id, String name, String description, double price) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.price = price;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

Creating a REST Controller with CRUD Operations

This controller demonstrates CRUD (Create, Read, Update, Delete) operations for the Product resource:

  • @RequestMapping("/products"): Maps all requests under the "/products" path to this controller.
  • @GetMapping: Retrieves all products. Returns a list of Product objects.
  • @GetMapping("/{id}"): Retrieves a product by its ID. Returns a Product object or a 404 Not Found if the product doesn't exist. Uses ResponseEntity to provide more control over the HTTP response (status code, headers, etc.).
  • @PostMapping: Creates a new product. Takes a Product object in the request body (using @RequestBody) and returns the created product with a 201 Created status code.
  • @PutMapping("/{id}"): Updates an existing product. Takes the product ID as a path variable and the updated Product object in the request body. Returns the updated product or a 404 Not Found.
  • @DeleteMapping("/{id}"): Deletes a product. Takes the product ID as a path variable and returns a 204 No Content status code if the product is successfully deleted, or a 404 Not Found if the product doesn't exist.

Important: This example uses an in-memory list to store products. In a real-world application, you would typically use a database.

package com.example.restapi.controller;

import com.example.restapi.model.Product;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/products")
public class ProductController {

    private List<Product> products = new ArrayList<>();

    @GetMapping
    public List<Product> getAllProducts() {
        return products;
    }

    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        Optional<Product> product = products.stream().filter(p -> p.getId().equals(id)).findFirst();
        if(product.isPresent()){
            return new ResponseEntity<>(product.get(), HttpStatus.OK);
        }
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    @PostMapping
    public ResponseEntity<Product> createProduct(@RequestBody Product product) {
        product.setId((long) (products.size() + 1));
        products.add(product);
        return new ResponseEntity<>(product, HttpStatus.CREATED);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product updatedProduct) {
        List<Product> collect = products.stream().filter(product -> !product.getId().equals(id)).collect(Collectors.toList());

        Optional<Product> productToUpdate = products.stream().filter(p -> p.getId().equals(id)).findFirst();
        if(productToUpdate.isPresent()){
            Product product = productToUpdate.get();

            product.setName(updatedProduct.getName());
            product.setDescription(updatedProduct.getDescription());
            product.setPrice(updatedProduct.getPrice());

            collect.add(product);
            products = collect;
            return new ResponseEntity<>(product, HttpStatus.OK);
        }
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<HttpStatus> deleteProduct(@PathVariable Long id) {
        Optional<Product> productToDelete = products.stream().filter(p -> p.getId().equals(id)).findFirst();
        if(productToDelete.isPresent()){
            products.remove(productToDelete.get());
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}

Concepts Behind the Snippet

This snippet illustrates fundamental RESTful API concepts:

  • Resource Orientation: The API focuses on resources (e.g., products) identified by URLs.
  • HTTP Methods: It uses standard HTTP methods (GET, POST, PUT, DELETE) to perform CRUD operations.
  • Statelessness: Each request from the client to the server must contain all of the information needed to understand the request, and cannot take advantage of any stored context on the server.
  • Representational State Transfer (REST): It transfers representations of resources between the client and server, usually in JSON format.
  • ResponseEntity: ResponseEntity allows you to control the HTTP status codes, headers and body of the response.

Real-Life Use Case Section

Imagine building an e-commerce application. You would need APIs for:

  • Managing product catalogs (like the example above).
  • Handling user accounts.
  • Processing orders.
  • Managing inventory.

Each of these would be a RESTful API with its own set of resources and endpoints.

Best Practices

  • Use meaningful resource names: Use nouns, not verbs (e.g., /products, not /getProducts).
  • Use HTTP status codes correctly: Return appropriate status codes (e.g., 200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Internal Server Error).
  • Implement proper error handling: Provide informative error messages in a consistent format.
  • Use pagination for large datasets: Avoid returning very large lists of resources.
  • Secure your API: Implement authentication and authorization to protect your data. Spring Security is a good choice for this.
  • Version your API: Use versioning (e.g., /v1/products) to avoid breaking changes for existing clients.
  • Use HATEOAS (Hypermedia as the Engine of Application State): Include links in your responses to allow clients to discover related resources. Spring HATEOAS provides support for this.

Interview Tip

When discussing RESTful APIs in an interview, be prepared to explain the principles of REST, the different HTTP methods, and how you would handle error handling, security, and versioning. Also, be ready to discuss your experience with different REST frameworks like Spring Boot.

When to use them

RESTful APIs are suitable when you need:

  • A simple and lightweight way to expose data and functionality.
  • To support a wide range of clients (web browsers, mobile apps, other services).
  • A stateless communication protocol.

Alternatives

Alternatives to RESTful APIs include:

  • GraphQL: A query language for your API that allows clients to request only the data they need.
  • gRPC: A high-performance, open-source universal RPC framework.
  • SOAP: A more complex and heavyweight protocol than REST.

Pros

  • Simple and lightweight.
  • Widely adopted and well-understood.
  • Scalable and flexible.
  • Can be used with a variety of data formats (JSON, XML, etc.).

Cons

  • Can be verbose.
  • Difficult to discover available resources (without HATEOAS).
  • Can lead to over-fetching or under-fetching of data.

FAQ

  • What is the difference between @Controller and @RestController?

    @Controller is a generic annotation for marking a class as a web controller. It typically returns the name of a view to be rendered by a view resolver. @RestController is a specialization of @Controller that combines @Controller and @ResponseBody. Methods in a @RestController directly return data in the response body (usually JSON or XML).

  • What is the purpose of the @RequestBody annotation?

    The @RequestBody annotation is used to bind the body of the HTTP request to a method parameter. It's typically used with POST and PUT requests to receive data from the client.

  • How do you handle errors in a Spring Boot REST API?

    You can handle errors in a Spring Boot REST API using a combination of exception handlers and ResponseEntity. You can create a global exception handler using @ControllerAdvice and @ExceptionHandler to handle specific exceptions and return appropriate error responses.

  • How do you implement pagination in a Spring Boot REST API?

    You can implement pagination by using Pageable object as a parameter. Here an example:
    @GetMapping
    public Page<Product> getAllProducts(Pageable pageable) {
    return repository.findAll(pageable);
    }