Simulating ChatGPT‑Style Typing with Spring WebFlux and SSE
This tutorial demonstrates how to use Spring WebFlux’s reactive streaming to create a ChatGPT‑like typing effect, covering backend setup, SSE integration, frontend Axios handling, and a comparison between Flux and traditional Server‑Sent Events.
Environment: Spring Boot 3.0.9
1. Introduction
This article explores how to use Spring WebFlux to simulate the character‑by‑character typing effect seen in ChatGPT. By leveraging WebFlux’s non‑blocking, real‑time data transmission, developers can create smooth, live user experiences for modern web applications.
2. Environment Preparation
Server side
Include the WebFlux starter dependency instead of the traditional servlet‑based starter:
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency></code>Do not import both spring-boot-starter-web and spring-boot-starter-webflux together; the servlet starter will take precedence.
Frontend
The front end uses Axios for asynchronous HTTP requests.
3. Practical Example
Create a simple WebFlux endpoint that emits 20 messages, each delayed by one second:
<code>@GetMapping("")
public Flux<String> message() {
return Flux
.range(1, 20)
.map(n -> "message - " + n + "<br/>")
.delayElements(Duration.ofMillis(1000));
}</code>The result is a stream of messages displayed one after another.
Next, set up a static resource directory /sse with an index.html file and configure Spring to serve it:
<code>@Configuration
public class StaticResourceConfig implements WebFluxConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/sse/**")
.addResourceLocations("classpath:/sse/");
}
}</code>Sample index.html (simplified):
<code><html>
<head>
<meta charset="utf-8">
<title>WebFlux Typing Effect</title>
</head>
<body>
<h2>WebFlux Typing Effect</h2>
</body>
</html></code>Define a controller that receives plain‑text content, splits it into characters, and streams each character with a one‑second delay:
<code>@RestController
@RequestMapping("/sse")
public class SseController {
@PostMapping(value = "", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> message(@RequestBody String content) {
return Flux
.just(content)
.flatMap(c -> Flux.just(c.split("")))
.map(n -> "message - " + n + ", ")
.delayElements(Duration.ofMillis(1000));
}
}</code>Front‑end HTML for input and result display:
<code><div>
<h4>Result</h4>
<div class="result"></div>
</div>
<div>
<table>
<tr>
<td>
<textarea id="content"></textarea>
</td>
<td>
<button type="button" onclick="submitContent()">Submit</button>
</td>
</tr>
</table>
</div></code>JavaScript using Axios to post the content and handle progress events:
<code>const instance = axios.create({
baseURL: 'http://localhost:8080',
timeout: 10000
});
function submitContent() {
let content = document.querySelector('#content').value;
instance.post('/sse', content, {
headers: {'Content-Type': 'text/plain'},
onDownloadProgress(progressEvent) {
// Decode only the newly received bytes
const text = progressEvent.event.currentTarget.responseText;
const encoder = new TextEncoder();
const decoder = new TextDecoder('UTF-8');
const byteArray = encoder.encode(text)
.subarray(progressEvent.loaded - progressEvent.bytes, progressEvent.loaded);
console.log(decoder.decode(byteArray));
}
}).then(resp => console.log(resp)).catch(ex => console.error(`${ex}`));
}</code>The above approach avoids the issue where each progress event contains the entire accumulated response; by extracting only the newly received bytes, the typing effect appears character by character.
Attempting to set responseType: 'stream' in the browser fails because the XMLHttpRequest spec does not support a "stream" response type; this mode is only available in Node.js environments.
4. Flux vs. SSE
WebFlux returns a Flux , which is a reactive stream that can push multiple data items over time, keeping the connection open for continuous delivery. SSE (Server‑Sent Events) is a simpler HTTP‑based protocol that also pushes data but offers fewer features and is limited to plain text events.
Both technologies enable server‑to‑client push, yet they differ in data handling, reconnection logic, and extensibility.
Finished!
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.