React client receives only HTTP 401 without JSON error body, while Postman shows full 401 JSON response when JWT is expired
08:25 10 Dec 2025

I am developing a Spring Boot application using JWT authentication.

When an access token is expired, the backend should return:

  • HTTP status: 401

  • JSON error body, for example:

    {
        "statusCode": 401,
        "errorMessage": "Expired token."
    }
    
    

In Postman, this works correctly for all API routes. Postman always receives both the 401 status code and the JSON error message.

However, in my React frontend, the behavior is different:

  1. When the access token is expired, the browser receives only the 401 status code.

  2. The JSON error body is missing in the response.

  3. Despite the frontend being implemented to send a refresh-token (reissue) request only when both the 401 status AND the JSON error message appear, the frontend still sends the reissue request even though the JSON body is missing.

  4. Only one specific API route (an Excel download endpoint) returns both the 401 status and the JSON error body correctly in the frontend.
    All other routes return only the bare 401 status.

  5. In the browser network tab, the flow looks like this:

    • First request returns only 401 with an empty response body.

    • Then the frontend sends the reissue request.

    • The reissue response returns normally.

My JWT filter:

@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws IOException, ServletException {

    String token = jwtTokenProvider.resolveToken(request);

    if (token != null && !jwtTokenProvider.isTokenExpired(token)) {
        Authentication authentication = jwtTokenProvider.getAuthentication(token);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    if (token != null && jwtTokenProvider.isTokenExpired(token)) {
        throw ExpiredTokenException.EXCEPTION;
    }

    filterChain.doFilter(request, response);
}

The ExpiredTokenException is handled by a global exception handler, which normally returns a JSON response body with the error details.

What I want to understand:

  • Why the React frontend receives only the status code (401) with an empty body.

  • Why Postman receives both the status code and the JSON message correctly.

  • Why only one specific API route returns the JSON error body correctly, but others do not.

  • Why the frontend sends the refresh-token request even though the error body is missing.

  • What backend behavior might cause the browser to receive a bare 401 response while Postman does not.

No assumptions or interpretations included here; this is exactly what is happening.

Any explanation or guidance would be appreciated.enter image description here

json spring jwt