When we want to write raw streaming bytes to the output, our handler method should return an instance of StreamingResponseBody instead of ResponseBodyEmitter.
ResponseBodyEmitter writes the output through an HttpMessageConverter, whereas StreamingResponseBody writes directly to the response OutputStream.
StreamingResponseBody is preferable when streaming videos or large files..
StreamingResponseBody is a functional interface with just one method.
Definition of StreamingResponseBodyVersion: 7.0.6 package org.springframework.web.servlet.mvc.method.annotation;
@FunctionalInterface
public interface StreamingResponseBody {
void writeTo(OutputStream outputStream)
throws IOException; 1
}
Using this interface is very similar to using Callable for async processing. Spring takes care of thread management, handler method just needs to return an implementation of StreamingResponseBody.
Example
The controller
package com.logicbig.example;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.io.IOException;
import java.io.OutputStream;
@Controller
public class TestController {
@GetMapping("/test")
public StreamingResponseBody handleRequest() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream out) throws IOException {
for (int i = 0; i < 50; i++) {
out.write((i + " - ").getBytes());
out.flush();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
}
}
Configuration Class
@EnableWebMvc
@Configuration
@ComponentScan
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setThreadNamePrefix("mvc-async-");
executor.initialize();
configurer.setTaskExecutor(executor);
}
}
Running the example
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
Using curl
$ curl -s "http://localhost:8080/test" 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 45 - 46 - 47 - 48 - 49 -
This GIF shows how the curl command streams output in real time:
Like ResponseBodyEmitter, StreamingResponseBody can also be used as the body of ResponseEntity:
package com.logicbig.example;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.io.IOException;
import java.io.OutputStream;
@Controller
public class TestController2 {
@GetMapping("/test2")
public ResponseEntity<StreamingResponseBody> handleRequest () {
StreamingResponseBody responseBody = new StreamingResponseBody() {
@Override
public void writeTo (OutputStream out) throws IOException {
for (int i = 0; i < 50; i++) {
out.write((Integer.toString(i) + " - ")
.getBytes());
out.flush();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
return new ResponseEntity(responseBody, HttpStatus.OK);
}
}
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 4.2.0.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- spring-test 7.0.6 (Spring TestContext Framework)
- junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
- hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
- JDK 25
- Maven 3.9.11
|