Why is my custom exception not returning in JSON format?

Viewed 584

I have connected a Spring applications to a React front-end where I need to display my custom exceptions. The custom exceptions work perfectly in Spring, but the front-end (React) only receives the error code (417) and nothing else.

I have determined that the problem is that the exception is not being returned in JSON format because the error message is displayed in its entirety when I use Postman, but not in JSON format.

My research has shown that since I am using a @RestController (for my main controller) and @ControllerAdvice (for my custom exception handler) that it should be returning in JSON format. I also tried adding a ResponseBody bean to the specific function but that did not help either.

Controller.java

package com.chess.controller;

import com.chess.board.*;
import com.chess.gameflow.Game;
import com.chess.gameflow.Move;
import com.chess.gameflow.Player;
import com.chess.gameflow.Status;
import com.chess.models.requests.BoardRequest;
import com.chess.models.requests.PlayerRequest;
import com.chess.models.requests.StatusRequest;
import com.chess.models.responses.MovesResponse;
import com.chess.models.responses.Response;
import com.chess.models.responses.StatusResponse;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@CrossOrigin(origins= "http://localhost:3000", maxAge=7200)
@RestController
@RequestMapping("/game")
public class Controller {
    Game game;
    Board board = Board.boardConstructor();


    @PostMapping("/players")
    public List<Response> createPlayer(@RequestBody PlayerRequest request){
        game = new Game(request.getName1(), request.getName2());
        List<Response> returnValue = board.returnBoard();
        Player player1= Game.players[0];
        StatusResponse status = new StatusResponse(Status.isActive(), Status.isCheck(), player1);
        returnValue.add(status);
        return returnValue;
    }

    @PostMapping
    public List<Response> makeMove(@RequestBody BoardRequest boardRequest){        
        StatusResponse status = Game.run(boardRequest);
        List<Response> returnValue = board.returnBoard();
        returnValue.add(status);
        return returnValue;
    }

    @PostMapping("/end")
    public StatusResponse endGame(@RequestBody StatusRequest statusRequest){
        Status.setActive(false);
        Board board = Board.boardConstructor();
        board.generateBoard();
        if (statusRequest.isForfeit()){
            StatusResponse statusResponse = new StatusResponse(statusRequest.getPlayerName() + " declares defeat! Game Over!");
            return statusResponse;
        }
        StatusResponse statusResponse = new StatusResponse("We have a draw! Good Game!");
        return statusResponse;
    }

    @GetMapping("/moves")
    public MovesResponse displayMoves(){
        MovesResponse movesResponse = new MovesResponse(Move.returnMoveMessages());
        return movesResponse;
    }
}

CustomExceptionsHandler.java

package com.chess.exceptions;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class CustomExceptionsHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value = InvalidMoveException.class)
    @ResponseBody //this line shouldn't be necessary as I am using a RestController but I added it anyways in one of my futile attempts and I don't think it should hurt
    protected ResponseEntity<Object> resolveInvalidMove(InvalidMoveException e, WebRequest req) throws Exception {   
        ErrorResponse errorResponse = new ErrorResponse(HttpStatus.EXPECTATION_FAILED.value(),
                HttpStatus.EXPECTATION_FAILED.getReasonPhrase(),
                e.getMessage(),
                req.getDescription(true));
        return handleExceptionInternal(e, errorResponse.toString(), new HttpHeaders(), HttpStatus.EXPECTATION_FAILED, req);


    @ExceptionHandler(value = MustDefeatCheckException.class)
    protected ResponseEntity<Object> resolveCheckStatus(MustDefeatCheckException e, WebRequest req){
        ErrorResponse errorResponse = new ErrorResponse(HttpStatus.EXPECTATION_FAILED.value(),
                HttpStatus.EXPECTATION_FAILED.getReasonPhrase(),
                e.getMessage(),
                req.getDescription(true));
        return handleExceptionInternal(e, errorResponse, new HttpHeaders(), HttpStatus.EXPECTATION_FAILED, req);
    }
}

ErrorResponse.java

package com.chess.exceptions;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "error")
public class ErrorResponse {
    private int status;
    private String errReason;
    private String errMessage;
    private String path;

    public ErrorResponse(int status, String errReason, String errMessage, String path) {
        this.status = status;
        this.errReason = errReason;
        this.errMessage = errMessage;
        this.path = path;
    }

    @Override
    public String toString(){
        return "Status Code: "  + status + " " + errReason + " Message: " + errMessage + " at " + path;
    }
}

InvalidMoveException.java

package com.chess.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.EXPECTATION_FAILED, reason="Invalid Move")
public class InvalidMoveException extends RuntimeException {
    public InvalidMoveException(String msg){ super(msg); }
}
1 Answers

I solved half the problem. Instead of returning a ResponseEntity with handleExceptionInternal, I was able to return the ErrorResponse itself by making it a child of Response which is the father to all my regular responses.

So now my CustomExceptionsHandler.java looks like this-

@RestControllerAdvice
public class CustomExceptionsHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value = InvalidMoveException.class)
    @ResponseStatus(HttpStatus.EXPECTATION_FAILED)
    public ErrorResponse resolveInvalidMove(InvalidMoveException e, WebRequest req) {
        ErrorResponse errorResponse = new ErrorResponse(HttpStatus.EXPECTATION_FAILED.value(),
                HttpStatus.EXPECTATION_FAILED.getReasonPhrase(),
                e.getMessage(),
                req.getDescription(true));
        return errorResponse;
    }
}

And I am getting the exception in JSON format when I use Postman. However, my error in React is unchanged. I am still only getting the status code and no other information. enter image description here

Board.js

 DataService.makeMove(move)
            .then(res => {
                //console.log(res.data);
                setIsWhite((prev) => !prev);                
                props.setTheBoard(res.data);
                setStatus(res.data[64]);
                updateMovesList();
            })
            .catch(err => {
                console.log(err)
                console.log(err.errMessage)
                console.log(err.message)
                console.log(err.status)
                console.log(err.errReason)
            })

DataService.js

    makeMove(move){
        return axios.post(`${url}`, move);
    }

I always thought catching errors was very simple but apparently I am missing something

Related