I'd like to return 404 when the response object is null for every response automatically in spring boot.
I need suggestions.
I don't want to check object in controller that it is null or not.
I'd like to return 404 when the response object is null for every response automatically in spring boot.
I need suggestions.
I don't want to check object in controller that it is null or not.
You need more than one Spring module to accomplish this. The basic steps are:
@ControllerAdvice
that catches the custom exception and translates it into an HTTP 404
status code.Step 1: Exception class
public class ResourceNotFoundException extends RuntimeException {}
Step 2: Controller advice
@ControllerAdvice
public class ResourceNotFoundExceptionHandler
{
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public void handleResourceNotFound() {}
}
Step 3: AspectJ advice
@Aspect
@Component
public class InvalidRepositoryReturnValueAspect
{
@AfterReturning(pointcut = "execution(* org.example.data.*Repository+.findOne(..))", returning = "result")
public void intercept(final Object result)
{
if (result == null)
{
throw new ResourceNotFoundException();
}
}
}
A sample application is available on Github to demonstrate all of this in action. Use a REST client like Postman for Google Chrome to add some records. Then, attempting to fetch an existing record by its identifier will return the record correctly but attempting to fetch one by a non-existent identifier will return 404
.
Simplest way to do this in Spring is write your own exception class like below
@ResponseStatus(value = HttpStatus.NOT_FOUND)
class ResourceNotFoundException extends RuntimeException{
}
Then just throw the ResourceNotFoundException from anywhere.
if (something == null) throw new ResourceNotFoundException();
For more -> Read
There is no need to throw exceptions, now ResponseBodyAdvice
does the trick:
@ControllerAdvice
public class NullTo404 implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (body == null) {
response.setStatusCode(HttpStatus.NOT_FOUND);
}
return body;
}
}
Similarly, you can implement ResponseBodyAdvice<Optional<?>>
, and check for Optional.isEmpty()
before setting the response status. It has the added benefit of working nicely with CrudRepository
. Most controller methods eventually ends like this:
public Optional<Product> getProductBySku(@PathVariable String sku) {
// logic goes here...
return productRepository.findBySku(sku);
}
Similar to @manish's answer (https://stackoverflow.com/a/43891952/986160) but without the AspectJ pointcut and using another @ControllerAdvice
instead:
Step 1: NotFoundException class:
public class NotFoundException extends RuntimeException {
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException() {}
}
Step 2: Check if body returned in endpoint is null and throw NotFoundException:
@ControllerAdvice
public class NotFoundAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
@SuppressWarnings("unchecked")
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body == null) {
throw new NotFoundException("resource not found");
}
return body;
}
}
Step 3: handle NotFoundException and make the response have a status of 404
@ControllerAdvice
public class GlobalExceptionAdvice {
@Data
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
super();
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
}
@ExceptionHandler(NotFoundException.class)
public final ResponseEntity<ErrorDetails> notFoundHandler(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
}
Alternative to Step 3:
You can just annotate your NotFoundException
with @ResponseStatus
and override fillInStackTrace()
(from https://stackoverflow.com/a/31263942/986160) so that it has similar effect to GlobalExceptionAdvice
and doesn't show stacktrace like this:
@ResponseStatus(value = HttpStatus.NOT_FOUND,reason = "resource not found")
public class NotFoundException extends RuntimeException {
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException() {}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}