Spock + spring boot web - get exception message - java

So, I'm using spring boot web and I want to test this method:
private void testException(HotelTvApp app) {
throw new InternalServerException("Test message");
}
Returning custom exception:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalServerException extends RuntimeException {
public InternalServerException(String message) {
super(message);
}
}
A controller that invokes this method returns following JSON data:
{
"timestamp": 1558423837560,
"status": 500,
"error": "Internal Server Error",
"message": "Test message",
"path": "/api/v1/test"
}
I want to write a test that will check if the exception message is correct. I tried this:
def "createRoom() should return 500 if trying to create room with already existing number"() {
given:
def uriRequest = UriComponentsBuilder.fromPath("/api/v1/test")
when:
def perform = mvc.perform(get(uriRequest.toUriString()))
then:
perform.andExpect(status().isInternalServerError())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath('$.message', is("Test message")))
}
But I get this exception:
Caused by: java.lang.AssertionError: Content type not set
How can I check the exception message here?

You can explicitly set the contentType as below
#ExceptionHandler(OrgNotFoundException.class)
public ResponseEntity<String> exceptionHandler(final Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body("Error");
}

Related

Can I create a custom exception handler for ResponseStatusException?

I'm throwing response status exceptions in my codebase that look like this:
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Resource not found!");
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad request!");
But, it generates this default spring json error:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "Resource not found",
"instance": "/api/graphql",
"properties": null
}
I'd like to create my own exception handler because I don't want to show the fields: type, instance & properties.
I've tried creating my own exception handler for the ResponseStatusException.class here:
#ControllerAdvice
public class ResponseStatusHandler extends ResponseStatusExceptionHandler {
#Autowired
private ObjectMapper objectMapper;
#ExceptionHandler({ResponseStatusException.class})
public ResponseEntity<Object> handle(ResponseStatusException e) {
ObjectNode node = objectMapper.createObjectNode()
.put("code", e.getStatusCode().value())
.put("message", e.getMessage());
return ResponseEntity.status(e.getStatusCode())
.contentType(MediaType.APPLICATION_JSON)
.body(node);
}
}
But, it never seems to get invoked and only the default spring response is returned. Is it possible to add a custom exception handler for ResponseStatusException?

How to validate Exception in spring-webflux WebTestClient tests?

How can I validate the actual Exception in a spring-webflux test?
The following worked in the old spring-web environment, but migrating to netty and spring-webflux, the MvcResult cannot be resolved anymore (NullPointerException):
#SpringBootTest
#AutoConfigureWebTestClient
public class ApiTest {
#Test
public void test() throws Exception {
webTestClient.get()
.uri("/api?test=123")
.exchange()
.expectStatus().isBadRequest()
.expectBody().consumeWith(rsp -> {
//throws NPE
Exception ex = ((MvcResult) rsp.getMockServerResult()).getResolvedException();
assertTrue(ex instanceof ResponseStatusException);
});
}
}
#RestController
public class ApiController {
#PostMapping("/api")
public String test(#RequestParam String test) {
if (test.matches("[0-9]+"))
throw new ResponseStatusException(HttpStatus.BadRequest, "Prohibited characters");
}
}
How could I still validate the real exception class?
The main purpose of the WebTestClient is to test endpoints using fluent API to verify responses. There is no magic deserialization or error handling happening but you can get access to the raw responses (status, headers, body).
In your example you will not get MvcResult or ResponseStatusException but you could get access to the raw body using rsp.getResponseBody() that would look like
{
"timestamp": "2022-05-17T17:57:07.041+00:00",
"path": "/api",
"status": 400,
"error": "Bad Request",
"requestId": "4fa648d"
}
You could use expectBody().consumeWith(rsp -> { ... }) to get access to the request and response or expectBody(String.class).value(body -> { ... })to get just body. As an alternative use some fluent API to validate result JSON.expectBody().json()or.expectBody().jsonPath()` to check specific fields only.
In addition you could still deserialize body explicitly using .expectBody(Response.class).value(body -> {...}).

Override not-found exception handler in micronaut

I'm trying to override the not-found exception handler in micronaut.
I want to throw my custom error format, but cannot find which exception handler I've to replace.
My current implementation:
#Produces
#Singleton
#Requires(classes = {Exception.class, ExceptionHandler.class})
//#Replaces(NotFoundExceptionHandler.class)
public class MyNotFoundExceptionHandler implements ExceptionHandler<Exception, HttpResponse<?>> {
#Override
public HttpResponse<?> handle(HttpRequest request, Exception exception) {
return HttpResponse.notFound();
}
}
The default micronaut exception handler throws this format on a not-found:
{
"message": "Page Not Found",
"_links": {
"self": {
"href": "/api/not-found",
"templated": false
}
}
}
I'm already overridden other default exception handlers, like ConstraintExceptionHandler and UnsatisfiedRouteHandler, but for the not-found the logger doesn't print the exception in the console:
20:43:23.863 [default-nioEventLoopGroup-1-5] DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/not-found
20:43:23.864 [default-nioEventLoopGroup-1-5] DEBUG i.m.h.s.netty.RoutingInBoundHandler - No matching route: GET /api/not-found
20:43:23.864 [default-nioEventLoopGroup-1-5] DEBUG i.m.web.router.RouteMatchUtils - Route match attribute for request (/api/not-found) not found
Anyone knows which exception handler to override?
you can find what you are looking for here https://guides.micronaut.io/latest/micronaut-error-handling-maven-java.html#global-error
With this code in any controller you should be ok:
#Error(status = HttpStatus.NOT_FOUND, global = true)
fun notFoundHandler(request: HttpRequest<*>): HttpResponse<JsonError> {
return HttpResponse.status<JsonError>(HttpStatus.NOT_FOUND).body(JsonError("Page '$request' Not Found"))
}

How to change Status code in spring boot error response?

I am making a simple rest service that makes some http calls and aggregates data using RestTemplate.
Sometimes i get NotFound error and sometimes BadRequest errors.
I want to respond with the same status code to my client and Spring seems to have this mapping out of the box. the message is okay but the Status code is always 500 Internal Server error.
I Would like to map my status code to the one i am initially receiving
"timestamp": "2019-07-01T17:56:04.539+0000",
"status": 500,
"error": "Internal Server Error",
"message": "400 Bad Request",
"path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
i would like it to be this way
"timestamp": "2019-07-01T17:56:04.539+0000",
"status": 400,
"error": "Internal Server Error",
"message": "400 Bad Request",
"path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
It throws HttpClientErrorException.BadRequest or HttpClientErrorException.NotFound
my code is a simple endpoint :
#GetMapping("/{id}")
public MyModel getInfo(#PathVariable String id){
return MyService.getInfo(id);
}
You can create global exception handling with #ControllerAdvice annotation. Like this:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = YourExceptionTypes.class)
protected ResponseEntity<Object> handleBusinessException(RuntimeException exception, WebRequest request) {
return handleExceptionInternal(exception, exception.getMessage(), new HttpHeaders(), HttpStatus.NOT_ACCEPTABLE, request);
}
}
When an exception is thrown, the handler will catch and transform it to the desired response. The original exception wont be propagated.
The accepted solution with the #ControllerAdvice is insufficient. That surely marks the response with the custom status code for the exception. It does, however, not return the wanted response body as JSON but as only simple string - the message from the exception.
To get the correct status code and the default error body the DefaultErrorAttributes can help.
#ControllerAdvice
public class PackedTemplateNotRecodableExceptionControllerAdvice extends ResponseEntityExceptionHandler {
#Autowired
private DefaultErrorAttributes defaultErrorAttributes;
#ExceptionHandler(PackedTemplateNotRecodableException.class)
public ResponseEntity<Object> handlePackedTemplateNotRecodableException(final RuntimeException exception, final WebRequest webRequest) {
// build the default error response
webRequest.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.BAD_REQUEST.value(), RequestAttributes.SCOPE_REQUEST);
final Map<String, Object> errorAttributes = defaultErrorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults());
// return the error response with the specific response code
return handleExceptionInternal(exception, errorAttributes, new HttpHeaders(), HttpStatus.BAD_REQUEST, webRequest);
}
}
That way you'll receive the wanted error response, e.g. something like this:
{
"timestamp": "2019-07-01T17:56:04.539+0000",
"status": 400,
"error": "Internal Server Error",
"message": "400 Bad Request",
"path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
I have spent a lot of time looking into this issue, including solutions from answers here, which didn't work for me (or I didn't implement correctly).
I finally got a breakthrough. Instead of throwing a generic Exception such as throw new Exception(message), I created classes that extends the Exception class for the specific exception type - with their respective HTTP error codes and message
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class BadRequestException extends Exception{
public BadRequestException(String message) {
super(message);
}
}
In your application logic, you can now throw the Bad Request exception with a message like so throw new BadRequestException("Invalid Email"). This will result in an exception thrown thus :
{
"timestamp": "2021-03-01T17:56:04.539+0000",
"status": 400,
"error": "Bad Request",
"message": "Invalid Email",
"path": "path to controller"
}
You can now create other custom exception classes for the different exceptions you want, following the above example and changing the value parameter in the #ResponseStatus, to match the desired response code you want. e.g for a NOT FOUND exception #ResponseStatus (value = HttpStatus.NOT_FOUND), Java provides the different HTTP status codes via the HttpStatus enum.
For more context
I hope this is detailed enough and helps someone :)
Possible duplicate of Spring Resttemplate exception handling Your code needs a controller advice to handle the exceptions from the service it is calling.

Netflix Feign - Propagate Status and Exception through Microservices

I'm using Netflix Feign to call to one operation of a Microservice A to other other operation of a Microservice B which validates a code using Spring Boot.
The operation of Microservice B throws an exception in case of the validation has been bad. Then I handled in the Microservices and return a HttpStatus.UNPROCESSABLE_ENTITY (422) like next:
#ExceptionHandler({
ValidateException.class
})
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
#ResponseBody
public Object validationException(final HttpServletRequest request, final validateException exception) {
log.error(exception.getMessage(), exception);
error.setErrorMessage(exception.getMessage());
error.setErrorCode(exception.getCode().toString());
return error;
}
So, when Microservice A calls to B in a interface as next:
#Headers("Content-Type: " + MediaType.APPLICATION_JSON_UTF8_VALUE)
#RequestLine("GET /other")
void otherOperation(#Param("other") String other );
#Headers("Content-Type: " + MediaType.APPLICATION_JSON_UTF8_VALUE)
#RequestLine("GET /code/validate")
Boolean validate(#Param("prefix") String prefix);
static PromotionClient connect() {
return Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(PromotionClient.class, Urls.SERVICE_URL.toString());
}
and the validations fails it returns a internal error 500 with next message:
{
"timestamp": "2016-08-05T09:17:49.939+0000",
"status": 500,
"error": "Internal Server Error",
"exception": "feign.FeignException",
"message": "status 422 reading Client#validate(String); content:\n{\r\n \"errorCode\" : \"VALIDATION_EXISTS\",\r\n \"errorMessage\" : \"Code already exists.\"\r\n}",
"path": "/code/validate"
}
But I need to return the same as the Microservice operation B.
Which would be the best ways or techniques to propagate Status and Exceptions through microservices using Netflix Feign?
You could use a feign ErrorDecoder
https://github.com/OpenFeign/feign/wiki/Custom-error-handling
Here is an example
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyBadRequestException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
For spring to pick up the ErrorDecoder you have to put it on the ApplicationContext:
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
Shameless plug for a little library I did that uses reflection to dynamically rethrow checked exceptions (and unchecked if they are on the Feign interface) based on an error code returned in the body of the response.
More information on the readme :
https://github.com/coveo/feign-error-decoder
OpenFeign's FeignException doesn't bind to a specific HTTP status (i.e. doesn't use Spring's #ResponseStatus annotation), which makes Spring default to 500 whenever faced with a FeignException. That's okay because a FeignException can have numerous causes that can't be related to a particular HTTP status.
However you can change the way that Spring handles FeignExceptions. Simply define an ExceptionHandler that handles the FeignException the way you need it (see here):
#RestControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(FeignException.class)
public String handleFeignStatusException(FeignException e, HttpServletResponse response) {
response.setStatus(e.status());
return "feignError";
}
}
This example makes Spring return the same HTTP status that you received from Microservice B. You can go further and also return the original response body:
response.getOutputStream().write(e.content());
Write your custom exception mapper and register it. You can customize responses.
Complete example is here
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable ex) {
return Response.status(500).entity(YOUR_RETURN_OBJ_HERE).build();
}
}
Since 2017 we've created a library that does this from annotations (making it fairly easy to, just like for requests/etc, to code this up by annotations).
it basically allows you to code error handling as follows:
#ErrorHandling(codeSpecific =
{
#ErrorCodes( codes = {401}, generate = UnAuthorizedException.class),
#ErrorCodes( codes = {403}, generate = ForbiddenException.class),
#ErrorCodes( codes = {404}, generate = UnknownItemException.class),
},
defaultException = ClassLevelDefaultException.class
)
interface GitHub {
#ErrorHandling(codeSpecific =
{
#ErrorCodes( codes = {404}, generate = NonExistentRepoException.class),
#ErrorCodes( codes = {502, 503, 504}, generate = RetryAfterCertainTimeException.class),
},
defaultException = FailedToGetContributorsException.class
)
#RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(#Param("owner") String owner, #Param("repo") String repo);
}
You can find it in the OpenFeign organisation:
https://github.com/OpenFeign/feign-annotation-error-decoder
disclaimer: I'm a contributor to feign and the main dev for that error decoder.
What we do is as follows:
Share common jar which contains exceptions with both microservices.
1.) In microservices A convert exception to a DTO class lets say ErrorInfo.
Which will contain all the attributes of your custom exception with a String exceptionType, which will contain exception class name.
2.) When it is received at microservice B it will be handled by ErrorDecoder in microservice B and It will try to create an exception object from exceptionType as below:
#Override
public Exception decode(String methodKey, Response response) {
ErrorInfo errorInfo = objectMapper.readValue(details, ErrorInfo.class);
Class exceptionClass;
Exception decodedException;
try {
exceptionClass = Class.forName(errorInfo.getExceptionType());
decodedException = (Exception) exceptionClass.newInstance();
return decodedException;
}
catch (ClassNotFoundException e) {
return new PlatformExecutionException(details, errorInfo);
}
return defaultErrorDecoder.decode(methodKey, response);
}

Categories

Resources