I have a rest web service developed with Spring Boot.I am able to handle all the exceptions that occur due to my code, but suppose the json object that the client posts is not compatible with the object that i want to desrialize it with, I get
"timestamp": 1498834369591,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "JSON parse error: Can not deserialize value
I wanted to know is there a way that for this exception, I can provide the client a custom exception message. I am not sure how to handle this error.
To customize this message per Controller, use a combination of #ExceptionHandler and #ResponseStatus within your Controllers:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "CUSTOM MESSAGE HERE")
#ExceptionHandler(HttpMessageNotReadableException.class)
public void handleException(HttpMessageNotReadableException ex) {
//Handle Exception Here...
}
If you'd rather define this once and handle these Exceptions globally, then use a #ControllerAdvice class:
#ControllerAdvice
public class CustomControllerAdvice {
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "CUSTOM MESSAGE HERE")
#ExceptionHandler(HttpMessageNotReadableException.class)
public void handleException(HttpMessageNotReadableException ex) {
//Handle Exception Here...
}
}
You also can extend ResponseEntityExceptionHandler and override the method handleHttpMessageNotReadable (example in Kotlin, but very similar in Java):
override fun handleHttpMessageNotReadable(ex: HttpMessageNotReadableException, headers: HttpHeaders, status: HttpStatus, request: WebRequest): ResponseEntity<Any> {
val entity = ErrorResponse(status, ex.message ?: ex.localizedMessage, request)
return this.handleExceptionInternal(ex, entity as Any?, headers, status, request)
}
Related
I'm creating a Restful API and I want JSON responses. I enabled Https by default and when I try to access endpoints with http protocol I get a 400 error with this response message:
Bad Request This combination of host and port requires TLS.
I want to return a JSON object instead, something like:
{
"message": "Bad Request HTTPS required",
"status": 400,
"timestamp": "2022-04-13T12:05:25.332"
}
How can I implement this in Spring Boot??
You can create custom Exception handler with the #ControllerAdvice annotation and cach any Exception, with the #ExceptionHandler annotation, and then return with a custom json response.
#ControllerAdvice
public class ApiExceptionHandler {
//The exception can be different or eather a list of exceptions like value = { ExceptionType1.class, ExceptionType2.class}
#ExceptionHandler(value = Exception.class)
public ResponseEntity<Object> handleBadRequestException(BadRequestException e) {
HttpStatus httpStatus = HttpStatus.OK;
CustomResponseClass response = new CustomResponseClass(CustomEnum.RESPONSE_CODE, e.getMessage(), httpStatus.value(), ZonedDateTime.now(ZoneId.of("Z")));
return new ResponseEntity<>(response, httpStatus);
}
}
I have a global exception handler, which works fine to capture exceptions thrown from my controller, service layer, or repository layer. However, it fails to capture exceptions that occur before entering my controller. Specifically, I have a POST controller that expects a valid json body, if the actual json body is malformed, an HttpMessageNotReadableException is thrown, and I have no idea where this exception got handled. The response code is indeed 400. So my question is, how to use my own logic to capture and handle message deserialization exception that happens before entering my controller.
My global exception handler (it works fine for exceptions thrown from my service layer)
#ControllerAdvice(basePackageClasses = TopologyApiController.class)
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final String UNKNOWN_SERVER_ERROR_MSG = "Unknown server error";
#ExceptionHandler(value = {ServiceException.class})
public ResponseEntity<ErrorResponse> handleServiceException(Exception ex, WebRequest request) {
// some handling
return generateExceptionResponseEntity(errorMessage, status);
}
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex, WebRequest request) {
return generateExceptionResponseEntity(UNKNOWN_SERVER_ERROR_MSG, HttpStatus.INTERNAL_SERVER_ERROR);
}
private ResponseEntity<ErrorResponse> generateExceptionResponseEntity(String message, HttpStatus status) {
ErrorResponse response = new ErrorResponse();
response.setMessage(message);
return ResponseEntity.status(status).body(response);
}
}
My POST controller (expects a json body to deserialize into a CityInfo object)
#RequestMapping(value = API_BASE + "/topology/cities", method = RequestMethod.POST)
ResponseEntity<CityInfo> topologyCitiesPost(#Valid #RequestBody CityInfo body) {
CityInfo cityInfo = topologyService.addCity(body);
return ResponseEntity.status(HttpStatus.CREATED).body(cityInfo);
}
The controller expects a json body in the form of below, and the entire code works fine if the json is valid.
{
"description": "string",
"name": "string",
"tag": "string"
}
but if the actual content is something like below (e.g., with several commas at the end), an HttpMessageNotReadableException will be thrown and is not captured by my handler.
{
"description": "this is description",
"name": "city name",
"tag": "city tag",,,,
}
This: So my question is, how to use my own logic to capture and handle message deserialization exception that happens before entering my controller.
Annotate and write an exceptionHandler for that specific exception. Add this to your GlobalExceptionHandler class:
#ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleMessageNotReadableException(Exception ex, WebRequest request) {
// some handling
return generateExceptionResponseEntity(errorMessage, status);
}
I have a consumer (RabbitListner) in RPC mode and I would like to know if it is possible to throw exception that can be treated by the publisher.
To make more clear my explication the case is as follow :
The publisher send a message in RPC mode
The consumer receive the message, check the validity of the message and if the message can not be take in count, because of missing parameters, then I would like to throw Exception. The exception can be a specific business exception or a particular AmqpException but I want that the publisher can handle this exception if it is not go in timeout.
I try with the AmqpRejectAndDontRequeueException, but my publisher do not receive the exception, but just a response which is empty.
Is it possible to be done or may be it is not a good practice to implement like that ?
EDIT 1 :
After the #GaryRussel response here is the resolution of my question:
For the RabbitListner I create an error handler :
#Configuration
public class RabbitErrorHandler implements RabbitListenerErrorHandler {
#Override public Object handleError(Message message, org.springframework.messaging.Message<?> message1, ListenerExecutionFailedException e) {
throw e;
}
}
Define the bean into a configuration file :
#Configuration
public class RabbitConfig extends RabbitConfiguration {
#Bean
public RabbitTemplate getRabbitTemplate() {
Message.addWhiteListPatterns(RabbitConstants.CLASSES_TO_SEND_OVER_RABBITMQ);
return new RabbitTemplate(this.connectionFactory());
}
/**
* Define the RabbitErrorHandle
* #return Initialize RabbitErrorHandle bean
*/
#Bean
public RabbitErrorHandler rabbitErrorHandler() {
return new RabbitErrorHandler();
}
}
Create the #RabbitListner with parameters where rabbitErrorHandler is the bean that I defined previously :
#Override
#RabbitListener(queues = "${rabbit.queue}"
, errorHandler = "rabbitErrorHandler"
, returnExceptions = "true")
public ReturnObject receiveMessage(Message message) {
For the RabbitTemplate I set this attribute :
rabbitTemplate.setMessageConverter(new RemoteInvocationAwareMessageConverterAdapter());
When the messsage threated by the consumer, but it sent an error, I obtain a RemoteInvocationResult which contains the original exception into e.getCause().getCause().
See the returnExceptions property on #RabbitListener (since 2.0). Docs here.
The returnExceptions attribute, when true will cause exceptions to be returned to the sender. The exception is wrapped in a RemoteInvocationResult object.
On the sender side, there is an available RemoteInvocationAwareMessageConverterAdapter which, if configured into the RabbitTemplate, will re-throw the server-side exception, wrapped in an AmqpRemoteException. The stack trace of the server exception will be synthesized by merging the server and client stack traces.
Important
This mechanism will generally only work with the default SimpleMessageConverter, which uses Java serialization; exceptions are generally not "Jackson-friendly" so can’t be serialized to JSON. If you are using JSON, consider using an errorHandler to return some other Jackson-friendly Error object when an exception is thrown.
What worked for me was :
On "serving" side :
Service
#RabbitListener(id = "test1", containerFactory ="BEAN CONTAINER FACTORY",
queues = "TEST QUEUE", returnExceptions = "true")
DataList getData() {
// this exception will be transformed by rabbit error handler to a RemoteInvocationResult
throw new IllegalStateException("mon expecion");
//return dataHelper.loadAllData();
}
On "requesting" side :
Service
public void fetchData() throws AmqpRemoteException {
var response = (DataList) amqpTemplate.convertSendAndReceive("TEST EXCHANGE", "ROUTING NAME", new Object());
Optional.ofNullable(response)
.ifPresentOrElse(this::setDataContent, this::handleNoData);
}
Config
#Bean
AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
var rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
MessageConverter jsonMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.registerModule(new JavaTimeModule());
var jsonConverter = new Jackson2JsonMessageConverter(objectMapper);
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = Map.of(
DataList.class.getName(), DataList.class,
RemoteInvocationResult.class.getName(), RemoteInvocationResult.class
);
classMapper.setIdClassMapping(idClassMapping);
jsonConverter.setClassMapper(classMapper);
// json converter with returned exception awareness
// this will transform RemoteInvocationResult into a AmqpRemoteException
return new RemoteInvocationAwareMessageConverterAdapter(jsonConverter);
}
You have to return a message as an error, which the consuming application can choose to treat as an exception. However, I don't think normal exception handling flows apply with messaging. Your publishing application (the consumer of the RPC service) needs to know what can go wrong and be programmed to deal with those possibilities.
In my grails app I have a custom InvalidTokenException as follows:
class InvalidTokenException extends Exception{
public InvalidTokenException() {}
public InvalidTokenException(String message)
{
super(message);
}
}
which I am throwing in my service as follows:
throw new InvalidTokenException("Invalid token : '${word}'")
which I am catching in my controller and rendering to the client as follows:
catch(e)
{
//send the exception to the client for rendering an error message.
render(status: 400, text: e)
return false //stops further execution
}
Though I want to be strip back the message that it only contains the text "Invalid token : word" rather than "Exception uk.co.litecollab.exceptions.InvalidTokenException Invalid token : word"
Any ideas how to do this?
Change it to render(status: 400, text: e.message) since you're currently taking advantage of the auto-conversion of e to a String, which calls the toString() method. Calling getMessage() directly makes more sense in general and does what you need here.
i need to catch all exceptions of my controllers to a exception controller. How to configure spring?
I need this because every request to my webapp are json request and in case of exception i need to answer with a genericc {success: false, exception: "String ex..."}. But i can not understand if the better way is to use SimpleMappingExceptionResolver.
Thank you.
If you want to write a custom response, it would be more interesting to use a custom HandlerExceptionResolver implementation.
spring configuration:
<bean id="exceptionHandler" class="com.am.CustomHandlerExceptionResolver"/>
java:
public class CustomHandlerExceptionResolver
implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//write in response
return null;
}
}