Spring Boot #ExceptionHandler with ResponseEntity<StreamingResponseBody> Does Not Work - java

Consider the following code
#RestController
#RequestMapping("/api")
class Controller {
#Autowired
private SomeService service;
#GetMapping("download")
public ResponseEntity<StreamingResponseBody> downloadCsv() {
// service throws some runtime exception
StreamingResponseBody body = outputStream -> service.writeToStream(outputStream);
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\"file.csv\"")
.contentType(MediaType.asMediaType(MediaType.parseMediaType("text/csv")))
.body(body);
}
/* Doesn't work */
#ExceptionHandler(PermissionDeniedException.class)
ResponseEntity<ErrorDetails> handlePermissionDeniedException(PermissionDeniedException e, HttpServletRequest req) {
ErrorDetails details = ....;
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorDetails);
}
/* Also doesn't work */
#ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Permission denied")
#ExceptionHandler(PermissionDenied.class)
void handlePermissionDeniedException2() {
}
}
In the case of a runtime exception being thrown from service.writeToStream(outputStream), the #ExceptionHandler for the exception does get called, however, the response returned by the controller does not have a body and will always have a HttpStatus 500.
If you do not return a ResponseEntity and instead return a StreamingResponseBody directly, everything works as expected.
/*
#ExceptionHandler works fine with this
*/
#GetMapping("download")
public StreamingResponseBody downloadCsv() {
// service throws some runtime exception
StreamingResponseBody body = outputStream -> service.writeToStream(outputStream);
return body;
}
Is there any way to make #ExceptionHandler work while wrapping the StreamingResponseBody in a ResponseEntity ?
Spring versions tried: 2.3.1.RELEASE, 2.3.2.RELEASE
Java Version: 11

Related

Not able to return ResponseEntity with Exception Details in spring

I have created a Spring Restful Service and Spring MVC application.
Restful Service ::
Restful service returns an entity if its existing in DB. If it doesn't exist It returns a custom Exception information in ResponseEntity object.
It is working as expected tested using Postman.
#GetMapping(value = "/validate/{itemId}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ResponseEntity<MyItem> validateItem(#PathVariable Long itemId, #RequestHeader HttpHeaders httpHeaders) {
MyItem myItem = myitemService.validateMyItem(itemId);
ResponseEntity<MyItem> responseEntity = null;
if (myItem == null) {
throw new ItemNotFoundException("Item Not Found!!!!");
}
responseEntity = new ResponseEntity<MyItem>(myItem, headers, HttpStatus.OK);
return responseEntity;
}
If the requested Entity does not exist Restful Service returns below.
#ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
System.out.println("In CREEH::ItemNFE");
ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<ExceptionResponse>(exceptionResponse, HttpStatus.NOT_FOUND);
return responseEntity;
}
But when I am calling the above service from a spring MVC application using RestTemplate, It is returning a valid object if it exists.
If the requested object does not exist Restful service is returning the exception information but its not reaching the calling(spring MVC) application.
Spring MVC application calls Restful Web Service using Rest template
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
This is expected behavior. Rest template throws exception when the http status is client error or server error and returns the response when http status is not error status.
You have to provide implementation to use your error handler, map the response to response entity and throw the exception.
Create new error exception class with ResponseEntity field.
public class ResponseEntityErrorException extends RuntimeException {
private ResponseEntity<ErrorResponse> errorResponse;
public ResponseEntityErrorException(ResponseEntity<ErrorResponse> errorResponse) {
this.errorResponse = errorResponse;
}
public ResponseEntity<ErrorResponse> getErrorResponse() {
return errorResponse;
}
}
Custom error handler which maps the error response back to ResponseEntity.
public class ResponseEntityErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return hasError(response.getStatusCode());
}
protected boolean hasError(HttpStatus statusCode) {
return (statusCode.is4xxClientError() || statusCode.is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpMessageConverterExtractor<ExceptionResponse> errorMessageExtractor =
new HttpMessageConverterExtractor(ExceptionResponse.class, messageConverters);
ExceptionResponse errorObject = errorMessageExtractor.extractData(response);
throw new ResponseEntityErrorException(ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(errorObject));
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}
RestTemplate Configuration - You have to set RestTemplate's errorHandler to ResponseEntityErrorHandler.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
ResponseEntityErrorHandler errorHandler = new ResponseEntityErrorHandler();
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
}
Calling Method
#Autowired restTemplate
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
try {
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
} catch (ResponseEntityErrorException re) {
ResponseEntity<ErrorResponse> errorResponse = re.getErrorResponse();
}
Try using the #ResponseBody annotation on your Exceptionhandler. e.g:
public #ResponseBody ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {... }
You should use Custom Exception Handler to fix your case. It looks like this
#ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
public CustomResponseEntityExceptionHandler() {
super();
}
// 404
#ExceptionHandler(value = { EntityNotFoundException.class, ResourceNotFoundException.class })
protected ResponseEntity<Object> handleNotFound(final RuntimeException ex, final WebRequest request) {
BaseResponse responseError = new BaseResponse(HttpStatus.NOT_FOUND.value(),HttpStatus.NOT_FOUND.name(),
Constants.HttpStatusMsg.ERROR_NOT_FOUND);
logger.error(ex.getMessage());
return handleExceptionInternal(ex, responseError, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}
}
And your code should throw some exception, eg:
if (your_entity == null) {
throw new EntityNotFoundException("said something");
}
If you get this case in somewhere else again, you just throw exception like above. Your handler will take care the rest stuffs.
Hope this help.
I've started your application and works just fine.
Maven :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
The controller class is :
#Controller
public class ValidationController {
#GetMapping(value = "/validate/{itemId}")
public #ResponseBody ResponseEntity<MyItem> validateItem(#PathVariable Long itemId) {
if (itemId.equals(Long.valueOf(1))) {
throw new ItemNotFoundException();
}
return new ResponseEntity<>(new MyItem(), HttpStatus.OK);
}
#ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
System.out.println("In CREEH::ItemNFE");
ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
return responseEntity;
}
}
and the test:
#RunWith(SpringRunner.class)
#WebMvcTest(value = ValidationController.class, secure = false)
public class TestValidationController {
#Autowired
private MockMvc mockMvc;
#Test
public void testExpectNotFound() throws Exception {
mockMvc.perform(get("/validate/1"))
.andExpect(status().isNotFound());
}
#Test
public void testExpectFound() throws Exception {
mockMvc.perform(get("/validate/2"))
.andExpect(status().isOk());
}
}
Are you sure the url you are trying to use with RestTemplate is correct?
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
Your get method is #GetMapping(value = "/validate/{itemId}"
If you don't have request mapping at the level of the controller the url should be:
http://localhost:8080/validate/1
Another difference is the missing #ResponseBody on your controller method.

Return HTTP code 200 from Spring REST API

I want to use this code to receive http link with values:
#PostMapping(value = "/v1/notification")
public String handleNotifications(#RequestParam("notification") String itemid) {
// parse here the values
return "result successful result";
}
How I can return http code 200 - successful response?
And also for example if there is a code exception into code processing how can I return error 404?
If you are using spring:
#PostMapping(value = "/v1/notification")
public ResponseEntity handleNotifications(#RequestParam("notification") String itemid) {
// parse here the values
return ResponseEntity.ok().build();
//OR ResponseEntity.ok("body goes here");
}
If you use #RestController it should return 200 by default.
But anyway, you can set a particular response status by #ResponseStatus annotation (even if the methods returns void) or you can return a custom response by ResponseEntity.
EDIT: added error handling
For error handling, you can return a particular response entity:
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body("some body ");
or you can use #ExceptionHandler:
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public void handleError(Exception ex) {
// TODO: log exception
}
You can do it by annotating your method with #ResponseStatus using HttpStatus.OK (However it should be 200 by default), like this:
Some controller
#PostMapping(value = "/v1/notification")
#ResponseStatus(HttpStatus.OK)
public String handleNotifications(#RequestParam("notification") String itemid) throws MyException {
if(someCondition) {
throw new MyException("some message");
}
// parse here the values
return "result successful result";
}
Now, in order to return a custom code when handling a specific exception you can create a whole separate controller for doing this (you can do it in the same controller, though) which extends from ResponseEntityExceptionHandler and is annotated with #RestControllerAdvice and it must have a method for handling that specific exception as shown below:
Exception handling controller
#RestControllerAdvice
public class ExceptionHandlerController extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
protected ResponseEntity<Object> handleMyException(MyException ex, WebRequest req) {
Object resBody = "some message";
return handleExceptionInternal(ex, resBody, new HttpHeaders(), HttpStatus.NOT_FOUND, req);
}
}
You can do something like this:
#PostMapping(value = "/v1/notification")
public ResponseEntity<String> handleNotifications(
#RequestParam("notification") String itemid) {
// parse here the values
return new ResponseEntity<>("result successful result",
HttpStatus.OK);
}

Spring boot MockMvc returning wrong status code

I am testing an #RestController which has an API endpoint such as /api/dataobject. If the object (in JSON format) that is posted to this endpoint is missing some part of its meta data, the API should respond with a Http status of bad request (400).
When testing it through Postman, this works, however in my unit test where the controller is mocked it still returns a status 200.
The method in the RestController:
#RequestMapping("/api/dataobject")
public ResponseEntity postDataObject(#RequestBody final DataObject dataObject) throws InvalidObjectException {
if (!dataObjectValidator.validateDataObject(dataObject)) {
throw new InvalidObjectException("Data object was invalid: " + dataObject.toString());
}
return new ResponseEntity(HttpStatus.OK);
}
The InvalidObjectException is caught by a class annotated with #ControllerAdvice which extends ResponseEntityExceptionHandler and is handled as follows:
#ExceptionHandler(value = InvalidObjectException.class)
protected ResponseEntity<Object> handleInvalidObject(final InvalidObjectException exception, final WebRequest request) {
final String bodyOfResponse = exception.getMessage();
return handleExceptionInternal(exception, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
Now, the unit test class is as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(DataObjectController.class)
public class DataObjectControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private DataObjectController dataObjectController;
private final String uri = "/api/idataobject";
#Test
public void noAppName() throws Exception {
DataObject object = getDataObjectNoAppName();
final String body = new Gson().toJson(object);
given(dataObjectController.postDataObject(object)).willReturn(new ResponseEntity(HttpStatus.BAD_REQUEST));
mvc.perform(post(uri)
.content(body)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
}
Even though the object is invalid, and I've said that the given object would return a HttpStatus 400, I get a 200 status in return.
Clearly I'm missing something here, but what?

spring boot unit test assertion error

Working on a spring boot based Rest project I have a controller like this
which calls service and service layer call dao layer. Now I am writing unit test code for controllers. when I run this the error says
java.lang.AssertionError: expected:<201> but was:<415>
I don't know where I am doing wrong:
public class CustomerController {
private static final Logger LOGGER = LogManager.getLogger(CustomerController.class);
#Autowired
private CustomerServices customerServices;
#Autowired
private Messages MESSAGES;
#Autowired
private LMSAuthenticationService authServices;
#RequestMapping(value = "/CreateCustomer", method = RequestMethod.POST)
public Status createCustomer(#RequestBody #Valid Customer customer, BindingResult bindingResult) {
LOGGER.info("createCustomer call is initiated");
if (bindingResult.hasErrors()) {
throw new BusinessException(bindingResult);
}
Status status = new Status();
try {
int rows = customerServices.create(customer);
if (rows > 0) {
status.setCode(ErrorCodeConstant.ERROR_CODE_SUCCESS);
status.setMessage(MESSAGES.CUSTOMER_CREATED_SUCCESSFULLY);
} else {
status.setCode(ErrorCodeConstant.ERROR_CODE_FAILED);
status.setMessage(MESSAGES.CUSTOMER_CREATION_FAILED);
}
} catch (Exception e) {
LOGGER.info("Cannot Create the Customer:", e);
status.setCode(ErrorCodeConstant.ERROR_CODE_FAILED);
status.setMessage(MESSAGES.CUSTOMER_CREATION_FAILED);
}
return status;
}
}
The test for the CustomerController.
public class CustomerControllerTest extends ApplicationTest {
private static final Logger LOGGER = LogManager.getLogger(CustomerControllerTest.class);
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#MockBean
private CustomerController customerController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Status status = new Status(200,"customer created successfully","success");
String customer = "{\"customerFullName\":\"trial8900\",\"customerPhoneNumber\": \"trial8900\", \"customerEmailID\": \"trial8900#g.com\",\"alternateNumber\": \"trial8900\",\"city\": \"trial8900\",\"address\":\"hsr\"}";
#Test
public void testCreateCustomer() throws Exception {
String URL = "http://localhost:8080/lms/customer/CreateCustomer";
Mockito.when(customerController.createCustomer(Mockito.any(Customer.class),(BindingResult) Mockito.any(Object.class))).thenReturn(status);
// execute
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(URL)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(TestUtils.convertObjectToJsonBytes(customer))).andReturn();
LOGGER.info(TestUtils.convertObjectToJsonBytes(customer));
// verify
MockHttpServletResponse response = result.getResponse();
LOGGER.info(response);
int status = result.getResponse().getStatus();
LOGGER.info(status);
assertEquals(HttpStatus.CREATED.value(), status);
}
}
HTTP status 415 is "Unsupported Media Type". Your endpoint should be marked with an #Consumes (and possibly also #Produces) annotation specifying what kinds of media types it expects from the client, and what kind of media type it returns to the client.
Since I see your test code exercising your production code with MediaType.APPLICATION_JSON_UTF8, you should probably mark your endpoint as consuming and producing APPLICATION_JSON_UTF8.
Then you also need to make sure that there is nothing terribly wrong going on in your error handling, because in the process of catching the exceptions generated by your production code and generating HTTP responses, your error handling code may be generating something different, e.g. generating an error status response with a payload containing an HTML-formatted error message, which would have a content-type of "text/html", which would not be understood by your test code which expects json.
Use the below base test class for your setUp and converting json to string and string to json
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = Main.class)
#WebAppConfiguration
public abstract class BaseTest {
protected MockMvc mvc;
#Autowired
WebApplicationContext webApplicationContext;
protected void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
protected String mapToJson(Object obj) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(obj);
}
protected <T> T mapFromJson(String json, Class<T> clazz)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, clazz);
}
}
Also verify that your post call has happened or not check the below sample
Mockito.doNothing().when(customerServices).create(Mockito.any(Customer.class));
customerServices.create(customer);
Mockito.verify(customerServices, Mockito.times(1)).create(customer);
RequestBuilder requestBuilder = MockMvcRequestBuilders.post(URI)
.accept(MediaType.APPLICATION_JSON).content(inputInJson)
.contentType(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
assertEquals(HttpStatus.OK.value(), response.getStatus());

Intercept #RequestHeader exception for missing header

I have a method in controller with has parameter for example
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(#RequestHeader("ETag") int etag)
If there is no ETag header in request - client gets 400 (BAD_REQUEST), which is not any informative.
I need to somehow handle this exception and send my own exception to client (I use JSON for this purpose).
I know that I can intercept exception via #ExceptionHandler, but in that case all HTTP 400 requests will be handled, but I want that have missing ETag in headers.
Any ideas?
You can also achieve this by use of annotation #ControllerAdvice from spring.
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler{
/**
* Handle ServletRequestBindingException. Triggered when a 'required' request
* header parameter is missing.
*
* #param ex ServletRequestBindingException
* #param headers HttpHeaders
* #param status HttpStatus
* #param request WebRequest
* #return the ResponseEntity object
*/
#Override
protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<>(ex.getMessage(), headers, status);
}
}
The response when you access your API without the required request header is:
Missing request header 'Authorization' for method parameter of type String
Like this exception, you can customise all other exceptions.
In case Spring version is 5+ then the exact exception you need to handle is the MissingRequestHeaderException. If your global exception handler class extends ResponseEntityExceptionHandler then adding an #ExceptionHandler for ServletRequestBindingException won't work because MissingRequestHeaderException extends ServletRequestBindingException and the latter is handled inside the handleException method of the ResponseEntityExceptionHandler. If you try you're going to get Ambiguous #ExceptionHandler method mapped for ... exception.
There are two ways to achieve what you are trying
First using #RequestHeader with required false
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(#RequestHeader(value="ETag", required=false) String ETag) {
if(ETag == null) {
// Your JSON Error Handling
} else {
// Your Processing
}
}
Second using HttpServletRequest instead of #RequestHeader
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(HttpServletRequest request) {
String ETag = request.getHeader("ETag");
if(ETag == null) {
// Your JSON Error Handling
} else {
// Your Processing
}
}
Write a method with the annotation #ExceptionHandler and use ServletRequestBindingException.class as this exception is thrown in case of missing header
For example :
#ExceptionHandler(ServletRequestBindingException.class)
public ResponseEntity<ResponseObject> handleHeaderError(){
ResponseObject responseObject=new ResponseObject();
responseObject.setStatus(Constants.ResponseStatus.FAILURE.getStatus());
responseObject.setMessage(header_missing_message);
ResponseEntity<ResponseObject> responseEntity=new ResponseEntity<ResponseObject>(responseObject, HttpStatus.BAD_REQUEST);
return responseEntity;
}
In Spring 5+ it is as simple as this. ErrorResponse is your own object to return
#RestControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(MissingRequestHeaderException.class)
public ResponseEntity<ErrorResponse> handleException(MissingRequestHeaderException ex) {
log.error("Error due to: " + ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse();
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
}
You should user an #ExceptionHandler method that looks if ETag header is present and takes appropriate action :
#ExceptionHandler(UnsatisfiedServletRequestParameterException.class)
public onErr400(#RequestHeader(value="ETag", required=false) String ETag,
UnsatisfiedServletRequestParameterException ex) {
if(ETag == null) {
// Ok the problem was ETag Header : give your informational message
} else {
// It is another error 400 : simply say request is incorrect or use ex
}
}
If you don't want to handle this in your request mapping, then you could create a Servlet Filter and look for the ETag header in the Filter. If it's not there, then throw the exception. This would apply to only requests that match your filter's URL mapping.
public final class MyEtagFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String etag = request.getHeader("ETag");
if(etag == null)
throw new MissingEtagHeaderException("...");
filterChain.doFilter(request, response);
}
}
You'll have to implement your own MissingEtagHeaderException, or use some other existing exception.
This is relatively simple. Declare two handler methods, one that declares the appropriate header in the #RequestMapping headers attribute and one that doesn't. Spring will take care to invoke the appropriate one based on the content of the request.
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST, headers = "ETag")
#ResponseStatus(HttpStatus.CREATED)
public void postWith(#RequestHeader("ETag") int etag) {
// has it
}
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void postWithout() {
// no dice
// custom failure response
}
You can also intercept the exception without extending ResponseEntityExceptionHandler:
#ControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(ServletRequestBindingException.class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex) {
// return a ResponseEntity<Object> object here.
}
}
You can add #Nullable to this request param, and in case of absence, request still enters the controller without throwing MissingRequestHeaderException, and you add manual validation to throw whatever you like in controller and handle in the ExceptionHandler.
You can create a custom exception class e.g. InvalidRequestHeaderException.java. You can customise your exception message here.
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class InvalidRequestHeaderException extends RuntimeException {
public InvalidRequestHeaderException() {
super("Invalid request header provided.");
}
}
In your controller, you can throw an exception if the header provided is invalid.
#RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void post(#RequestHeader("ETag") int etag) {
// some code
if (!isSupportedPlatform(platform)) {
throw new InvalidRequestHeaderException();
}
// some code
}
You can then create a ValidationHandler.java to handle these exceptions.
#RestControllerAdvice
public class ValidationHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
MissingRequestHeaderException.class,
InvalidRequestHeaderException.class
})
protected ResponseEntity<Object> handleRequestHeaderException(Exception ex) {
log.error(ex.getMessage());
return ResponseEntity.badRequest().body(ErrorResponse.builder()
.status(String.valueOf(HttpStatus.BAD_REQUEST.value()))
.reason(ex.getMessage()).build());
}
#AllArgsConstructor
#Getter
#Builder
public static class ErrorResponse {
private String status;
private String reason;
}
}
By using MissingRequestHeaderException, it will throw an exception if what you've annotated with #RequestHeader is missing, so you will get an exception like this:
Missing request header 'Etag' for method parameter of type int
And when the request header is present but not valid this exception will be thrown:
Invalid request header provided.

Categories