MediaTypeNotAcceptable with SpringBoot RestController - spring-boot

I have a rest controller with a GetMapping that produces media type "Plain_text". When an exception occurs in the underlying service, it will be handled by the controller advice and the controller advice returns an object that will be serialized to JSON.
In the happy path, where the service doesn't throw any exception, I'm getting a correct response. But in case of error scenarios, I'm getting an exception with error "Could not find acceptable representation". If I removed the produces tag, the controller is working fine.
Is there a way in spring boot to let an api return plain text media type and in case of errors, return a Json response?
Here is my code:
#RestController
#RequestMapping("/sample")
public class SampleController() {
#Autowired
SampleService service;
#GetMapping(produces = MediaType.TEXT_PLAIN)
public String getString(){
return service.getString();
}
}
ControllerAdvice:
#RestControllerAdvice
public class SampleControllerAdvice(){
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({SampleNotFoundException.class})
public SampleErrorResponse handleException(Exception ex) {
return new SampleErrorResponse(e.getMessage());
}
}

This looks related to SPR-16318, which has been fixed in Spring Framework 5.1 - this is the version used in Spring Boot 2.1.
You should upgrade to Spring Boot 2.1+ to get that fix in your application.

Related

Spring Reactive - Exception Handling for Method Not Allowed Exception not triggering post Spring 3.0.0 & Java 17 upgrade

We recently upgraded our Spring Reactive APIs that were running on Java 11 and Spring 2.7.x. Exceptions in the Controller layer are handled by a Global Exception Handler which also handled the Method Not Supported exception. Post the upgrade, we are getting internal server error instead of Method not allowed exception when we try a different HTTP verb other that the one that a specific endpoint is designated to.
Our application has both of the below dependencies:
spring-boot-starter-web
spring-boot-starter-webflux
Searched for some stack overflow links and tried adding the below piece of code but didn't help either.
#Component
#Order(-2)
public class RestWebExceptionHandler implements ErrorWebExceptionHandler {
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof HttpRequestMethodNotSupportedException) {
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
// marks the response as complete and forbids writing to it
return exchange.getResponse().setComplete();
}
return Mono.error(ex);
}
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<PlanResponse> handleHttpRequestMethodNotSupportedException(
final HttpRequestMethodNotSupportedException exception) {
return responseBuilderRegistry.getResponseBuilderByType(HttpRequestMethodNotSupportedResponseBuilder.class)
.buildResponse(exception);
That was a common issue that was "recently" addressed on Spring MVC and Spring Webflux.
If you are interested in it, this issue was discussed here https://github.com/spring-projects/spring-framework/issues/22991
Just created a project to test this, can you please try the following
#RestControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(MethodNotAllowedException.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public Mono<Void> handle(Exception e, ServerWebExchange exchange) {
return exchange.getResponse().setComplete();
}
}

Returning value from Apache Camel route to Spring Boot controller

I am calling a camel route from a Spring Boot controller. The camel route calls a REST service which returns a string value and I am trying to return that value from the camel route to the controller. Below is the Spring Boot controller:
#RestController
#RequestMapping("/demo/client")
public class DemoClientController {
#Autowired private ProducerTemplate template;
#GetMapping("/sayHello")
public String sayHello() throws Exception {
String response = template.requestBody("direct:sayHelloGet", null, String.class);
return response;
}
}
And below is my camel route:
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:sayHelloGet")
.log("Route reached")
.setHeader(Exchange.HTTP_METHOD, simple("GET"))
.to("http://localhost:8080/demo/sayHello")
.log("${body}");
}
}
In the route, the log is printing the return value from the REST service but that String is not returned to the controller. Can anyone please suggest how to return the value to the Spring Boot controller?
The Spring Boot version I am using is 2.2.5 and Apache Camel version is 3.0.1.
See this FAQ
https://camel.apache.org/manual/latest/faq/why-is-my-message-body-empty.html
The response from http is streaming based and therefore only readable once, and then its read via the log and "empty" as the response. So either
do not log
enable stream caching
convert the response from http to a string (not streaming and re-readable safe)

#RestControllerAdvice vs #ControllerAdvice

What are the major difference between #RestControllerAdvice and #ControllerAdvice ??
Is it we should always use #RestControllerAdvice for rest services and #ControllerAdvice MVC ?
#RestControllerAdvice is just a syntactic sugar for #ControllerAdvice + #ResponseBody, you can look here.
Is it we should always use #RestControllerAdvice for rest services and
#ControllerAdvice MVC?
Again, as mentioned above, #ControllerAdvice can be used even for REST web services as well, but you need to additionally use #ResponseBody.
In addition, we can just understand it as:
#RestControler = #Controller + #ResponseBody
#RestControllerAdvice = #ControllerAdvice + #ResponseBody.
Keeping in mind that #RestControllerAdvice is more convenient annotation for handling Exception with RestfulApi.
Example os usage:
#RestControllerAdvice
public class WebRestControllerAdvice {
#ExceptionHandler(CustomNotFoundException.class)
public ResponseMsg handleNotFoundException(CustomNotFoundException ex) {
ResponseMsg responseMsg = new ResponseMsg(ex.getMessage());
return responseMsg;
}
}
In that case any exception instanceOf CustomNotFoundException will be thrown in body of response.
Example extracted here:
https://grokonez.com/spring-framework/spring-mvc/use-restcontrolleradvice-new-features-spring-framework-4-3
Exception: A good REST API should handle the exception properly and send the proper response to the user. The user should not be rendered with any unhandled exception.
A REST API developer will have two requirements related to error handling.
Common place for Error handling
Similar Error Response body with a proper HTTP status code across APIs
#RestControllerAdvice is the combination of both #ControllerAdvice and #ResponseBody
The #ControllerAdvice annotation was first introduced in Spring 3.2.
We can use the #ControllerAdvice annotation for handling exceptions in the RESTful Services but we need to add #ResponseBody separately.
Note:
GlobalExceptionHandler was annotated with #ControllerAdvice, thus it is going to intercept exceptions from controllers accross the application.
The differences between #RestControllerAdvice and #ControllerAdvice is :
#RestControllerAdvice = #ControllerAdvice + #ResponseBody. - we can
use in REST web services.
#ControllerAdvice - We can use in both MVC and Rest web services, need to
provide the ResponseBody if we use this in Rest web services.
For Example :
Exception Class:
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends Exception{
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message){
super(message);
}
}
usage of the above exception in Rest Web Service.
#RestControllerAdvice
public class MyRestControllerAdviceHandler {
#ExceptionHandler(ResourceNotFoundException.class)
public ResponseMsg resourceNotFoundException(ResourceNotFoundException ex) {
ResponseMsg resMsg = new ResponseMsg(ex.getMessage());
return resMsg;
}
}
usage of the above exception in MVC.
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> resourceNotFoundException(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
If you use #ControllerAdvice and return your error object from a method then it will look for a view with the name of your error object so instead of returning the expected response it will return 404 for not founding a view page with that name
#ControllerAdvice
public class CustomizedExceptionHandler {
#ExceptionHandler({ UserNotFoundException.class })
#ResponseStatus(code = HttpStatus.BAD_REQUEST)
public ExceptionResponce handleUserNotException(Exception ex, WebRequest request) throws Exception {
ExceptionResponce exceptionResponce = new ExceptionResponce(new Date(), ex.getMessage(),
request.getDescription(false));
return exceptionResponce;
}
}
As in the above code, I want to return 400 (BAD_REQUEST) but
instead of 400, it is returning 404(NOT_FOUND)
You can solve this issue by using any of the below ways
add #ResponseBody to your method or class.
Use #RestControllerAdvice.
Or you can wrap your error object in ResponseEntity.
After using either of the above ways it returns the correct response

Stacktrace of exceptions in Spring Rest responses

I have few Rest web services implemented through Spring. The problem is that if any exception is thrown the webservice returns json object with formatted error message that contains stacktrace. Can I have a single point of handling exceptions, and return my custom json objects with messages that wouldn't contain stacktrace?
I see descriptions for spring mvc but im not really using that for building my views etc.
I know it's too late, but just pointing out some solutions that may help others!
case 1: if you're using application.properties file, add following line to your properties file.
server.error.include-stacktrace=on_trace_param
case 2: if you're using application.yml file, add following line to your yml file.
server:
error:
include-stacktrace: on_trace_param
case 3: In case, none of them works, try following changes:
Try to suppress the stack trace by overriding fillInStackTrace method in your exception class as below.
public class DuplicateFoundException extends RuntimeException {
#Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
ps1: I referred this article.
Spring provides an out of the box solution to handle all your custom exceptions from a single point. What you need is #ControllerAdvice annotation in your exception controller:
#ControllerAdvice
public class GlobalDefaultExceptionHandler {
#ExceptionHandler(Exception.class)
public String exception(Exception e) {
return "error";
}
}
If you want to go deep into Springs #ExceptionHandler at individual controller level or #ControllerAdvice at global application level here is a good blog.
To handle exceptions thrown from a spring application at a single point, this is the best way to do it. #ControllerAdvice will create an aspect join-point which will intercept all the exceptions with required matching types bound to the corresponding public method.Here, public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException dataIntegrityViolationException,
WebRequest request) is handling DataIntegrityViolationException thrown out of the system at one place.
#ControllerAdvice
public class GlobalControllerExceptionHandler {
private Logger logger = Logger.getLogger(this.getClass());
private HttpHeaders header = new HttpHeaders();
#Autowired
private MessageSource messageSource;
public GlobalControllerExceptionHandler() {
header.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
}
/**
* #param dataIntegrityViolationException
* #param request
* #return
*/
#ExceptionHandler({ DataIntegrityViolationException.class })
public ResponseEntity<?> handleDataIntegrityViolationException(DataIntegrityViolationException dataIntegrityViolationException,
WebRequest request) {
String message = ExceptionUtils.getMessage(dataIntegrityViolationException);
logger.error("*********BEGIN**************DataIntegrityViolationException******************BEGIN*******************\n");
logger.error(message, dataIntegrityViolationException.fillInStackTrace());
logger.error("*********ENDS**************DataIntegrityViolationException*******************ENDS*****************************\n");
return ResponseEntity.status(HttpStatus.CONFLICT).headers(header).body(dataIntegrityViolationException);
}
}

Spring 4 RestController - How to return jaxb object with ResponseEntity

I am using Spring #RESTController for my REST webservice. instead of returning the object of ModelAndView I am trying to return the object of ResponseEntity object in my rest method. for the Strgin type of response it is working ut when I am building ResponseEntity with a Jaxbobject it is giving me HTTP error 406
#RestController
#RequestMapping(value="/service")
public class MyController {
public #ResponseBody ResponseEntity<String> getDashBoardData() throws JAXBException {
// Some Operation
return new ResponseEntity<String>(myStringXML, responseHeaders, HttpStatus.OK);
}
}
Below is not working
#RestController
#RequestMapping(value="/service")
public class MyController {
public #ResponseBody ResponseEntity<MyJaxbClass> getDashBoardData() throws JAXBException {
// Some Operation
return new ResponseEntity<MyJaxbClass>(MyJaxbClassObject, HttpStatus.OK);
}
}
The #RestController annotation already implies the #ResponseBody annotation for all request handling methods, that is one of its purposes (it saves you from putting all those annotations there). So you can/should remove it.
Processing the return value of the method is done by a 'HandlerMethodReturnValueHandlerand the specific one which should handle this delegates to aHttpMessageConverter. It selects a specificHttpMessageConverterbased on the requested/supported response types for the current request and the support response types from theHandlerMethodReturnValueHandler`.
In general when using #EnableWebMvc or <mvc:annotation-driven /> everything should be setup automatically. The automatic setup does some detection on which libs are available (jaxb, json etc).
Based on the response code (406) you either have manually configured something wrong on the server side or the client doesn't support xml as a response type.

Resources