Hey folks! Here's another quick tip for you: today, we're diving into handling exceptions in Java and how we can smoothly manage errors across microservices (among other approaches). Let's learn together! 👨💻🚀
Some time ago, I came across an issue where I needed to pass an error to the BFF (Backend-for-frontend). In essence, we have three microservices: one BFF, and others that handle the business logic of the application. Errors triggered within the business logic need to at least return a message to the BFF. This allows the frontend to determine the best error message and the most appropriate way to handle that specific error.
In short, the approach is quite straightforward: the server sends an error code through a header, and both the frontend and BFF are responsible for reading this code and returning a formatted JSON response to the frontend. Here are the steps:
- Trigger an exception on the server, passing the error code.
- The ExceptionHandler handles this exception and inserts the error code into a header called "CODE".
- On the BFF side, this error is captured and a JSON response is constructed to be sent back to the frontend.
Here's a rough outline of how the code might look:
@Service
public class ServerService {
public Sucesso testMethod(Long id) {
if (id == 1) {
throw new ServerException(HttpStatus.BAD_REQUEST, ERROR_001);
} else if (id == 2) {
throw new ServerException(HttpStatus.BAD_REQUEST, ERROR_002);
} else if (id == 3) {
throw new ServerException(HttpStatus.BAD_REQUEST, ERROR_003);
}
return Sucesso.builder().id(id).build();
}
}
Afterward, on the server side, this is the class responsible for handling triggered errors and adding the error code into the respective header!
@RestControllerAdvice
public class ControlExceptionHandler {
public static final String CODE = "CODE";
@ExceptionHandler({ServerException.class})
public ResponseEntity<Object> serverException(ServerException e) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add(CODE, e.getCode());
return ResponseEntity.status(e.getHttpStatusCode()).headers(responseHeaders).build();
}
}
On our BFF side, we use the .onStatus()
method to check if an error occurred in the request and take appropriate actions (in this case, throwing a ClientException
based on the obtained response)."
@Service
public class ServerService {
public static final String CODE = "CODE";
private final RestClient restClient;
public ServerService(RestClient restClient) {
this.restClient = restClient;
}
public Sucesso testMethod(Long id) {
return restClient
.get()
.uri("http://localhost:8080?id=" + id)
.retrieve()
.onStatus(HttpStatusCode::isError, (HttpRequest request, ClientHttpResponse response) -> {
throw new ClientException(HttpStatus.BAD_REQUEST, Objects.requireNonNull(response.getHeaders().get(CODE)).get(0));
})
.body(Sucesso.class);
}
}
And then, still on the BFF side, we construct the response for the Frontend based on the header captured from the response.
@RestControllerAdvice
public class ControlExceptionHandler {
@ExceptionHandler({ClientException.class})
public ResponseEntity<Object> ClientException(ClientException e) {
return ResponseEntity.status(e.getHttpStatusCode()).body(e.getOnlyBody());
}
}
Finally, this is the result that we expect.
Here's the code on github: https://github.com/FelipeJansenDev/errors-between-microsservices
Follow me on Linkedin for more tips and tricks: https://www.linkedin.com/in/felipe-neiva-jansen/
Top comments (0)