Spring WebFlux — Error Handling

Prateek
2 min readMay 5, 2022

In this example, we’ll take a look at the Spring WebFlux Error Handling using @ControllerAdvice. While calling the services/micro-services anything could go wrong and result into 500 “Internal Server Error ” as shown below error:

{
"timestamp": "2022-05-05T03:42:10.982+00:00",
"path": "/employee/all",
"status": 500,
"error": "Internal Server Error",
"requestId": "3101b004-1"
}

Usually error messages like this will not be handled properly and would be propagated to all the downstream services which might impact user experience. In some cases, applications might want to use application specific error code to convey appropriate messages to the calling service.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>error-handling</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>error-handling</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

Employee.java

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class Employee {
private int id;
private String firstName;
private String lastName;
}

ErrorResponse.java

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class ErrorResponse {
private int errorCode;
private String message;
}

EmployeeNotFoundException.java

import lombok.Data;

@Data
public class EmployeeNotFoundException extends RuntimeException{
private final int employeeId;
private static final String MSG = "Employee Data not found";

public EmployeeNotFoundException(int employeeId) {
super(MSG);
this.employeeId = employeeId;
}
}

EmployeeService.java

import com.example.errorhandling.dto.Employee;
import com.example.errorhandling.exception.EmployeeNotFoundException;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Map;

@Service
public class EmployeeService {
private static final Map<Integer, Employee> DATA = Map.of(
1, new Employee(1, "Ravi", "Ashtikar"),
2, new Employee(2, "Neha", "Parate"),
3, new Employee(3, "Sumit", "Dekate"),
4, new Employee(4, "Megha", "Nimje"),
5, new Employee(5, "Harshita", "Kumbhare")
);

public Flux<Employee> getAllEmployees() {
return Flux.fromIterable(DATA.values())
.doFirst(() -> throwRandomError(true));
}

private void throwRandomError(boolean flag) {
if (flag) {
throw new RuntimeException("Some Errors!!");
}
}

public Mono<Employee> findEmployeeById(int id) {
return Mono.just(id)
.filter(e -> DATA.containsKey(e))
.map(e -> DATA.get(e))
.switchIfEmpty(Mono.error(() -> new EmployeeNotFoundException(id)));
}
}

To produce error, I set the value to true, so that we can see the error, make it false to be able to see the endpoints working.

doFirst(() -> throwRandomError(true));

Response:

[
{
"id": 3,
"firstName": "Sumit",
"lastName": "Dekate"
},
{
"id": 2,
"firstName": "Neha",
"lastName": "Parate"
},
{
"id": 1,
"firstName": "Ravi",
"lastName": "Ashtikar"
},
{
"id": 5,
"firstName": "Harshita",
"lastName": "Kumbhare"
},
{
"id": 4,
"firstName": "Megha",
"lastName": "Nimje"
}
]

--

--