Spring multipart file upload size - java

My application has following properties set
spring.http.multipart.max-file-size = 10MB
and
spring.http.multipart.max-request-size = 10MB
But instead of throwing the Springs default exception below
{ "message": "Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (12780451) exceeds the configured maximum (10485760)", "type": "MultipartException" }
I would like to throw CustomException with message like FILE_SIZE_TOO_BIG. Is this possible?
Can I use #ControllerAdvice for this ?

There're a lot of ways to handle exceptions in Spring application. See exception handling in Spring MVC topic. I think GlobalControllerExceptionHandler taken from the provided article will fit your requirements. Try this handler:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
#ExceptionHandler(MultipartException.class)
public void handleMultipart(MultipartException exception) {
// to do
}
}
If you get this class picked up by component scan, it should handle MultipartExceptions thrown from any controller.

Related

Spring ExceptionHandler but for normal beans

I have been able to successfully use #ExceptionHandler annonated methodsorg.springframework.web.bind.annotation.ExceptionHandler in Controller Classes in my Spring projects to handle exceptions thrown by spring #RestController
Working example:
#Validated
#RestController
#RequestMapping(value = UrlsProperties.API_PATH, produces = MediaType.APPLICATION_JSON, consumes = { MediaType.APPLICATION_JSON })
#Api(value = "MyController", description = "MyController processing and forwarding controller")
public class MyController {
private static Logger log = LogManager.getLogger(MyController.class);
...
#JsonFormat
#ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseMessage handleMissingParams(MissingServletRequestParameterException ex) {
String name = ex.getParameterName();
log.error(name + " parameter is missing");
return new ResponseMessage(400, ex.getMessage());
}
}
I am trying to achieve the same way of exception handling but for a normal bean, [ not a controller ]. Simply adding an #ExceptionHanlder annotated method did not seem to catch the exceptions thrown by that bean's methods.
My question is how to handle exceptions thrown by a bean by writing a method inside this bean?
#ExceptionHandler annotation is not for general exception handling. It's used in controllers to convert an exception into a proper HTTP response. It won't work for normal beans, because only controllers return a response.
If any code (doesn't need to be in a bean) throws an exception and you don't handle it, it would eventually propagate up to your controller's exception handler and it would be converted to a response. That would be poor design though, as you should handle exceptions as early as you can.
What you can do is create exceptions that are meant to be propagated to your exception handlers. Your code catches an exception, then re-throws it wrapped into your own exception (such as IllegalRequestException). The handler then returns an error code and details to the caller.

How could we use #ExceptionHandler with spring web flux?

In spring web we could use annotation #ExceptionHandler for handling server and client errors for controllers.
I've tried to use this annotation with web-flux controller and it still worked for me, but after some investigation I've found out here
The situation with Spring Web Reactive is more complicated. Because
the reactive streams are evaluted by a different thread than the one
that executes the controllers method, the exceptions won’t be
propagated to the controller thread automatically. This means that the
#ExceptionHandler method will work only for exceptions that are thrown
in the thread that handles the request directly. Exceptions thrown in
the stream will have to be propagated back to the thread if we want to
use the #ExceptionHandler feature. This seems like a bit of a let down
but at the time of writing this Spring 5 is still not released so
error handling might still get better.
So my question is how to propagate back exception to the thread. Is there a good example or article about using #ExceptionHandler and Spring web flux?
Updated:
From spring.io it looks like it's supported, but still lack general understanding
Thanks,
Now it is possible to use the #ExceptionHandler as well as #RestControllerAdvice or even #ControllerAdvice in Spring WebFlux.
Example:
Add the webflux dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Create your class ExceptionHandler
#RestControllerAdvice
public class ExceptionHandlers {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlers.class);
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String serverExceptionHandler(Exception ex) {
LOGGER.error(ex.getMessage(), ex);
return ex.getMessage();
}
}
Create a Controller
#GetMapping(value = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<String> exceptionReturn() {
return Mono.error(new RuntimeException("test error"));
}
Example extracted here:
https://ddcode.net/2019/06/21/spring-5-webflux-exception-handling/
You can use #ExceptionHandler annotated methods to handle errors that happen within the execution of a WebFlux handler (e.g., your controller method). With MVC you can indeed also handle errors happening during the mapping phase, but this is not the case with WebFlux.
Back to your exception propagation question, the article you're sharing is not accurate.
In reactive applications, the request processing can indeed hop from one thread to another at any time, so you can't rely on the "one thread per request" model anymore (think: ThreadLocal).
You don't have to think about exception propagation or how threads are managed, really. For example, the following samples should be equivalent:
#GetMapping("/test")
public Mono<User> showUser() {
throw new IllegalStateException("error message!");
}
#GetMapping("/test")
public Mono<User> showUser() {
return Mono.error(new IllegalStateException("error message!"));
}
Reactor will send those Exceptions as error signals as expected in the Reactive Streams contract (see the "error handling" documentation section for more on that).
not an exact answer to the original question, but a quick way to map your exceptions to http response status is to throw org.springframework.web.server.ResponseStatusException / or create your own subclasses...
Full control over http response status + spring will add a response body with the option to add a reason.
{
"timestamp": 1529138182607,
"path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
"status": 400,
"error": "Bad Request",
"message": "For input string: \"f7b.491bc\""
}
The following global error handler did the trick for me:
import org.springframework.web.server.ResponseStatusException;
#Slf4j
#RestControllerAdvice
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class MyCustomReactiveErrorHandling {
#ExceptionHandler(MyCustomNotFoundException.class)
public void handleMyCustomException(MyCustomNotFoundException ex) {
throw new ResponseStatusException(404, "Data not found!", ex);
}
}
Throwing my exceptions returns the correct http status code at the rest service.

Spring Boot handle SizeLimitExceededException

I use Spring boot 1.5.7.
I have not configured CommonsMultipartResolver, because Spring Boot handles file uploads already.
If my upload exceeds the maximum permited size, an ugly Exception is thrown.
This is handled by my controller.
#ControllerAdvice
public abstract class DefaultController implements InitializingBean {
#ExceptionHandler(Exception.class)
public ResponseEntity<ServiceException> handleException(final Exception ex) {
...
} else if (ex instanceof MultipartException) {
MultipartException me = (MultipartException) ex;
Throwable cause = ex.getCause();
if (cause instanceof IllegalStateException) {
Throwable cause2 = cause.getCause();
if (cause2 instanceof SizeLimitExceededException) {
// this is tomcat specific
SizeLimitExceededException slee = (SizeLimitExceededException) cause2;
}
}
}
This kind of handling is not only complex but also sadly Tomcat specific, because the SizeLimitExceededException is in the package org.apache.tomcat.util.http.fileupload.FileUploadBase.
How can I handle the error case, that some one uploads a bigger file then allowed and return a nice message, regardless which Servlet Engine is used?
You could define a exception handler method in your #ControllerAdvice which is specifically for MultipartException and then qualify it with a specific HttpStatus. For example:
#ExceptionHandler(MultipartException.class)
#ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
public ResponseEntity<ServiceException> handleMultipartException(MultipartException ex) {
...
}
This should allow you to focus on the 'maximum file size' exception without having to get into servlet container specifics.
Update 1 in response to this comment:
Sounds good, what about getPermittedSize and getActualSize provided by SizeLimitExceededException is there a chance to get this values not only if Tomcat is used?
By intercepting this error on the basis of (a) the exception type and (b) the HTTP status ... you are making the solution generally applicable. But in so doing you might lose the sort of detailed information that one servlet container (but perhaps not another) might give you. You could perhaps enforce your own maximum size by setting spring.http.multipart.max-file-size in which case you would be able to report 'permitted size' but if you want to report 'actual size' then you'll have to consider one of the following:
Have to use something provided by the servlet container
Choose a spring.http.multipart.max-file-size which is less thatn the supported maximum for your servlet container and then apply your own max size check inside your controller and throw your own specific exception type containing the actual and permitted sizes.
This working fine in my #RestControllerAdvice and the exception is more specific than MultipartException :
#ExceptionHandler(MaxUploadSizeExceededException.class)
#ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
public ResponseEntity handleMultipartException(MaxUploadSizeExceededException e) {
Map<String, String> result = new HashMap<>();
result.put("message", "Error ==> Large File ");
return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
.body(result);
}
PS : when using #RestControllerAdvice(annotations = RestController.class)
it doens't work .. you have to put #RestControllerAdvice without RestController.class annotation

Where does Spring Data REST build Exception JSON reply?

I'm using Spring Boot 1.5.3, Spring Data REST, Spring HATEOAS, Hibernate.
Spring Data REST manage in a pretty way exceptions, returning a well formatted JSON object like this:
{
"timestamp": "2017-06-24T16:08:54.107+0000",
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.dao.InvalidDataAccessApiUsageException",
"message": "org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved beforeQuery current operation : com.test.server.model.workflows.WorkSession.checkPoint -> com.test.server.model.checkpoints.CheckPoint; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved beforeQuery current operation : com.test.server.model.workflows.WorkSession.checkPoint -> com.test.server.model.checkpoints.CheckPoint",
"path": "/api/v1/workSessions/start"
}
I need to localize exception messages and I'd like to keep the same JSON format of Spring Data REST and take a look how they create the exception object.
I'm looking for the code where the exception is created in source code but I am not able to find that. Maybe ExceptionMessage is useful but it has not the structure of the object that at the end arrive to the user.
Where is the point where the exception is created?
Finally I found a useful link before I didn't see: Modify default JSON error response from Spring Boot Rest Controller.
So the object I was looking for is here: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorAttributes.java
there is an answer , from useful link
As described in the documentation on error handling, you can provide
your own bean that implements ErrorAttributes to take control of the
content.
here is example from documentation :
#ControllerAdvice(basePackageClasses = FooController.class)
public class FooControllerAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(YourException.class)
#ResponseBody
ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
just inject Locale into handleControllerException method and MessageSource into advice , and in handleControllerException get localize exception messages that you need

Spring Boot - Error Controller to handle either JSON or HTML

I have a spring boot application.
I have a custom error controller, that is mapped to using ErrorPage mappings. The mappings are largely based on HTTP Status codes, and normally just render a HTML view appropriately.
For example, my mapping:
#Configuration
class ErrorConfiguration implements EmbeddedServletContainerCustomizer {
#Override public void customize( ConfigurableEmbeddedServletContainer container ) {
container.addErrorPages( new ErrorPage( HttpStatus.NOT_FOUND, "/error/404.html" ) )
}
And my error controller:
#Controller
#RequestMapping
public class ErrorController {
#RequestMapping( value = "/error/404.html" )
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public String pageNotFound( HttpServletRequest request ) {
"errors/404"
}
This works fine - If I just enter a random non-existent URL then it renders the 404 page.
Now, I want a section of my site, lets say /api/.. that is dedicated to my JSON api to serve the errors as JSON, so if I enter a random non-existent URL under /api/.. then it returns 404 JSON response.
Is there any standard/best way to do this? One idea I tried out was to have a #ControllerAdvice that specifically caught a class of custom API exceptions I had defined and returned JSON, and in my standard ErrorController checking the URL and throwing an apprpriate API exception if under that API URL space (but that didn't work, as the ExceptionHandler method could not be invoked because it was a different return type from the original controller method).
Is this something that has been solved?
The problem was my own fault. I was trying to work out why my #ExceptionHandler was not able to catch my exception and return JSON - As I suggested at the end of my question, I thought I was having problems because of conflicting return types - this was incorrect.
The error I was getting trying to have my exception handler return JSON was along the lines of:
"exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
"message": "Could not find acceptable representation"
I did some more digging/experimenting to try to narrow down the problem (thinking that the issue was because I was in the Spring error handling flow and in an ErrorController that was causing the problem), however the problem was just because of the content negotiation stuff Spring does.
Because my errorPage mapping in the web.xml was mapping to /error/404.html, Spring was using the suffix to resolve the appropriate view - so it then failed when I tried to return json.
I have been able to resolve the issue by changing my web.xml to /error/404 or by turning off the content negotiation suffix option.
Now, I want a section of my site, lets say /api/.. that is dedicated
to my JSON api to serve the errors as JSON, so if I enter a random
non-existent URL under /api/.. then it returns 404 JSON response.
Is there any standard/best way to do this? One idea I tried out was to
have a #ControllerAdvice that specifically caught a class of custom
API exceptions I had defined and returned JSON, and in my standard
ErrorController checking the URL and throwing an apprpriate API
exception if under that API URL space (but that didn't work, as the
ExceptionHandler method could not be invoked because it was a
different return type from the original controller method).
I think you need to rethink what you are trying to do here. According to HTTP response codes here
The 404 or Not Found error message is an HTTP standard response code
indicating that the client was able to communicate with a given
server, but the server could not find what was requested.
So when typing a random URL you may not want to throw 404 all the time. If you are trying to handle a bad request you can do something like this
#ControllerAdvice
public class GlobalExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ErrorResponse> noRequestHandlerFoundExceptionHandler(NoHandlerFoundException e) {
log.debug("noRequestHandlerFound: stacktrace={}", ExceptionUtils.getStackTrace(e));
String errorCode = "400 - Bad Request";
String errorMsg = "Requested URL doesn't exist";
return new ResponseEntity<>(new ErrorResponse(errorCode, errorMsg), HttpStatus.BAD_REQUEST);
}
}
Construct ResponseEntity that suites your need.

Categories

Resources