Return 404 for every null response
You need more than one Spring module to accomplish this. The basic steps are:
- Declare an exception class that can be used to throw an exception when a repository method does not return an expected value.
- Add a
@ControllerAdvice
that catches the custom exception and translates it into an HTTP404
status code. - Add an AOP advice that intercepts return values of repository methods and raises the custom exception when it finds the values not matching expectations.
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
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;
}
}