0

I spent way too much time on this I thought I would share what I found out. I'm surprised I couldn't find this information anywhere as it would seem like something ever app must have.

I expect to be able to send additional data back to the client from my Spring Boot REST API when an error occurs. Something like this:

>curl -i GET https://api.twitter.com/1.1/statuses/update.json?include_entities=true
HTTP/1.1 400 Bad Request
date: Fri, 14 Jul 2023 15:23:34 GMT
content-type: application/json; charset=utf-8
cache-control: no-cache, no-store, max-age=0
content-length: 62

{"errors":[{"code":215,"message":"Bad Authentication data."}]}

I could get this working with curl, but in a browser, the response data was not received. I confirmed this using Google Dev Tools to view the network traffic. And the data was being sent from my REST API.

I would have expected to find some useful information here at one of these links, but I didn't.

Error Handling for REST with Spring

Best Practices for REST API Error Handling

tdemay
  • 649
  • 8
  • 23

2 Answers2

0

The solution was a CORS related problem. Adding Access-Control-Allow-Origin to the header was the solution.

Here's what you need to do if you want to catch all exceptions globally and send back a meaningful response. Forgive me as I'm new to this, so if there is something I'm missing, please let me know.

Global exception handler:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler
    public ResponseEntity<UserResponseException> handleException(Exception ex) {
        return ResponseException.ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
    }
    // Add more handlers as needed. 
}

and my response exception class

@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ResponseException {
    private int status;
    private List<String> messages;
    // add anything else you want to return

    public static ResponseEntity<ResponseException> ResponseEntity(HttpStatus status, List<String> messages) {
        ResponseException ure = new ResponseException(status.value(), messages);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Access-Control-Allow-Origin", "*");
        return new ResponseEntity<ResponseException>(ure, headers, status);
    }
}

Now I can get back the data and it's available in CURL and in WebBrowser and most importantly to my front-end client.

curl -i http://localhost:8080/api/users/test
HTTP/1.1 409
Access-Control-Allow-Origin: *
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 14 Jul 2023 05:45:19 GMT

{"status":422,"messages":["Email address is invalid: john.doegmail.com", "Phone number is invalid: '2323', expecting in format '999-999-9999'", "Invalid State 'xx'. A valid US state is required."]}
tdemay
  • 649
  • 8
  • 23
0

It is not necessary to set the Access-Control-Allow-Origin header manually. The @CrossOrigin annotation can be used to configure CORS directly on a controller or any handler method.

@CrossOrigin("*")
@RestControllerAdvice
public class GlobalExceptionHandler {
    // ...
}
Unmitigated
  • 76,500
  • 11
  • 62
  • 80
  • doesn't that pose a security risk? – tdemay Jul 15 '23 at 18:08
  • @tdemay Why would it? It sets the headers for you. Unless you're referring to the use of `*`, which was just taken from your example. – Unmitigated Jul 15 '23 at 18:09
  • I don't think I understand this CORS stuff very well. I assume the @CrossOrigin("*") annotation will allow all traffic in, even cross script. But putting it on the response was different. – tdemay Jul 15 '23 at 22:52
  • @tdemay You're also allowing all traffic by setting `Access-Control-Allow-Origin` to `*` instead of a specific origin. `@CrossOrigin` is just a shorter way of setting the response headers on all the handler methods. Why do you think your frontend is able to make the request after setting that header when you didn't explicitly whitelist only the origin of your frontend? – Unmitigated Jul 15 '23 at 22:55
  • it was always able to make the request. With and without it. The problem I had was I could see the results being sent back in fiddler, but google inspect said there was no response data. – tdemay Jul 16 '23 at 03:10
  • @tdemay Alright, but there is no difference between setting the CORS headers manually and using `@CrossOrigin`. It is not idiomatic with Spring Boot to set those headers manually, though. – Unmitigated Jul 16 '23 at 03:15