DEV Community

Cover image for Spring streaming response made easy
Elattar Saad
Elattar Saad

Posted on

Spring streaming response made easy

In certain scenarios, we need to retrieve large volumes of data, yet we often experience delays before the first pieces of the response are displayed. Fortunately, this is a well-known problem, and an effective solution exists.

TechStack

This project is built using:

  • Java 24.
  • Spring boot 3.5.5 with WebFlux.
  • Postgres (or any DB you want)

Use case

Simply, we need 1 million products (json objects) in the GET endpoint.

this is a simple SQL script to create and fill the products table:

-- Create table
CREATE TABLE IF NOT EXISTS products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price NUMERIC(10,2) NOT NULL
);

-- Insert 100,000 products
INSERT INTO products (name, price)
SELECT
    'Product ' || i,
    ROUND((RANDOM() * 1000)::numeric, 2)
FROM generate_series(1, 1000000) AS s(i);
Enter fullscreen mode Exit fullscreen mode

Next the entity and JPA repository:

@Entity
@Table(name = "products")
public class Product {
    @Id
    private Long id;
    private String name;
    private Double price;

    public Product() {
    }

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

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Double getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return "Product[" +
                "id=" + id + ", " +
                "name=" + name + ", " +
                "price=" + price + ']';
    }
}
Enter fullscreen mode Exit fullscreen mode
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}
Enter fullscreen mode Exit fullscreen mode

Any database can be used here, I'm just used to postgres due to my daily work.

Traditional endpoints

Nothing fancy for our traditional endpoint, a simple find all GET:

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

    private final ProductRepository productRepository;

    public ProductController(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @GetMapping
    public List<Product> getAll(){
       return productRepository.findAll();
    }
}
Enter fullscreen mode Exit fullscreen mode

While using this endpoint, the client needs to wait before exploiting the first returned product, or even worse, the response size is too big to be handled properly:

Tradition endpoint

Streaming Response to the rescue

Using StreamingResponseBody, we can stream our products one by one and save some time :)

@GetMapping("/stream")
    public StreamingResponseBody getAllStreamed(HttpServletResponse response){
       response.setContentType("text/event-stream");
       return outputStream -> {
           productRepository.findAll()
                    .forEach(product -> {
                        try {
                            String json = new ObjectMapper().writeValueAsString(product) + "\n";
                            outputStream.write(json.getBytes());
                            outputStream.flush();
                        } catch (JsonProcessingException e) {
                            log.error("Error parsing product to json", e);
                        } catch (IOException e) {
                            log.error("Error writing object to stream", e);
                        }
                    });
    };
}
Enter fullscreen mode Exit fullscreen mode

Stream endpoint

On another level

You may already noticed that we loop over the products and write one by one to the output stream, this can be improved even more by streaming data end-2-end from the data source to the client; For that purpose solutions like Spring Webflux and JPA streams exist.
(The resources are just below :D)

Resources

Streaming with JPA Stream<T>

Server-Sent Events (SSE) / Reactive

Read this article and more on my website: https://www.saadelattar.me/article/spring-response-streams

Top comments (0)