I have a web application with spring in which I do some file upload. Under eclipse, using Jetty (the maven plugin) it works perfectly. But when I deploy the application under Tomcat it does not and I get the following Exception :
org.springframework.web.bind.MissingServletRequestParameterException: Required org.springframework.web.multipart.MultipartFile parameter 'file' is not present
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.raiseMissingParameterException(AnnotationMethodHandlerAdapter.java:545)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveRequestParam(HandlerMethodInvoker.java:336)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:207)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:132)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:326)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:313)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:875)
Here is my form :
<form method="post" action="../admin/import.html" id="import"
enctype="multipart/form-data">
<div id="importInmates" align="center">
<input type="file" name="file" id="file"
data-dojo-type="dijit.form.Button"
label="<fmt:message key='import.file' />" />
<button data-dojo-type="dijit.form.Button" id="importInmates"
type="submit">
<fmt:message key="import.import" />
</button>
</div>
<input type="hidden" name="importType" value="inmates" />
</form>
And here is the intercepting method :
#RequestMapping(value = IMPORT_PAGE, method = RequestMethod.POST)
public String recieveFile(#RequestParam("importType") String importType,
#RequestParam("file") MultipartFile multipartFile, final HttpSession session)
{
if (multipartFile.getSize() < 0)
{
LOGGER.debug("No file has been uploaded");
return "redirect:.." + IMPORT_PAGE;
}
File file = new File("tmp");
try
{
multipartFile.transferTo(file);
BufferedReader lec = new BufferedReader(new FileReader(file));
LOGGER.debug(lec.readLine());
lec.close();
}
catch (Exception e)
{
LOGGER.error("An exception occured while reading " + importType + " file", e);
}
return "redirect:.." + IMPORT_PAGE;
}
I have added the following bean :
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000000"></property>
</bean>
both in applicationContext.xml and mvc-servlet.xml even if I think that only the latter is important.
Any help would be appreciated.
Thanks.
Thanks to #Bart I was able to find the following simple solution :
In the intercepting method, use #ModelAttribute instead of #RequestParam :
#RequestMapping(value = IMPORT_PAGE, method = RequestMethod.POST)
public String recieveFile(#RequestParam("importType") String importType,
#ModelAttribute("file") UploadedFile uploadedFile, final HttpSession session)
{
MultipartFile multipartFile = uploadedFile.getFile();
Where UploadedFile is the following class :
public class UploadedFile
{
private String type;
private MultipartFile file;
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
public void setFile(MultipartFile file)
{
this.file = file;
}
public MultipartFile getFile()
{
return file;
}
}
And it's working !!
Thanks everyone for your help.
Use #RequestPart instead of #RequestParam.
From the source:
Annotation that can be used to associate the part of a "multipart/form-data" request
with a method argument. Supported method argument types include {#link MultipartFile}
in conjunction with Spring's {#link MultipartResolver} abstraction,
{#code javax.servlet.http.Part} in conjunction with Servlet 3.0 multipart requests,
or otherwise for any other method argument, the content of the part is passed through an
{#link HttpMessageConverter} taking into consideration the 'Content-Type' header
of the request part. This is analogous to what #{#link RequestBody} does to resolve
an argument based on the content of a non-multipart regular request.
Note that #{#link RequestParam} annotation can also be used to associate the
part of a "multipart/form-data" request with a method argument supporting the same
method argument types. The main difference is that when the method argument is not a
String, #{#link RequestParam} relies on type conversion via a registered
{#link Converter} or {#link PropertyEditor} while #{#link RequestPart} relies
on {#link HttpMessageConverter}s taking into consideration the 'Content-Type' header
of the request part. #{#link RequestParam} is likely to be used with name-value form
fields while #{#link RequestPart} is likely to be used with parts containing more
complex content (e.g. JSON, XML).
Related
I am developing a simple app that use spring boot and jpa and thymeleaf, i need to upload the image to my database but when i click on submit on my page fields get inserted in database except image field. i read the different posts on the website but none of them was really close to my problem and i have no idea why it does not insert the file into the database. i have to say the image field located in recipe entity
controller
#Controller
#RequestMapping("/recipe")
public class RecipeController {
RecipeRepository recipeRepository;
IngredientRepository ingredientRepository;
public RecipeController(RecipeRepository recipeRepository, IngredientRepository ingredientRepository) {
this.recipeRepository = recipeRepository;
this.ingredientRepository = ingredientRepository; //// this is other repo which cause no problem
}
#GetMapping("/insert_recipe")
public String insetRecipe(Model model){
model.addAttribute("addRecipe",new Recipe());
model.addAttribute("addingredient",new Ingredient()); // this is other entity which cause no problem
return "insert_recipe";
}
#PostMapping("/postrecipe")
public String postRecipe(#ModelAttribute("addRecipe")#Valid Recipe recipe, BindingResult result, Model model, #ModelAttribute("addingredient") Ingredient ingredient) {
recipeRepository.save(recipe);
long id=recipe.getId();
Recipe u=recipeRepository.findById(id);
//model.addAttribute("addingredient",recipe);
ingredient.setRecipe(u);
ingredientRepository.save(ingredient);
return "redirect:/recipe/insert_recipe";
}
}
view page
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form th:action="#{/recipe/postrecipe}" th:object="${addRecipe}" enctype="multipart/form-data" method="post" >
des: <input type="text" name="descriptiob"/>
serving: <input type="text" name="servings"/>
for ingredient description <input type="text" name="description" th:object="${addingredient}">
upload picture <input type="file" th:name="image">
<input type="submit" value="submit">
</form>
<br/><br/>
</body>
</html>
repo
public interface RecipeRepository extends CrudRepository<Recipe,Long> {
Recipe findById(long is);
}
entity
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String descriptiob;
#Lob
private Byte[] image;
private Integer servings;
//setter and getter method also are in this class
error
Field error in object 'addRecipe' on field 'image': rejected value [org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile#12c96ba6]; codes [typeMismatch.addRecipe.image,typeMismatch.image,typeMismatch.[Ljava.lang.Byte;,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addRecipe.image,image]; arguments []; default message [image]]; default message [Failed to convert property value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile' to required type 'java.lang.Byte[]' for property 'image'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile' to required type 'java.lang.Byte' for property 'image[0]': PropertyEditor [org.springframework.beans.propertyeditors.CustomNumberEditor] returned inappropriate value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile']]
GitHub link
Let's have a look at the thymeleaf fragment
upload picture <input type="file" th:name="image">
and the error message we get:
Field error in object 'addRecipe' on field 'image': (...)
Cannot convert value of type '(...) StandardMultipartHttpServletRequest$StandardMultipartFile'
(...)to required type 'java.lang.Byte' for property 'image[0]': PropertyEditor (...)
upload picture <input type="file" th:name="image">
The name image colides with the Recipe field which has a different type (Byte[] than the MultipartFile we are trying to pass in the request).
One way to do it may be:
Step I. Change the th:name="image" to something else (that does not collide with the field names), e.g. th:name="imagefile"
upload picture <input type="file" th:name="imagefile">
Step II. Change the #RequestParam name to imagefile and convert the MultipartFile to the Byte[] before saving it.
#PostMapping("/postrecipe")
public String postRecipe(#ModelAttribute("addRecipe") Recipe recipe,
Model model,
#ModelAttribute("addingredient")#Valid Ingredient ingredient,
BindingResult bindingResult,
#RequestParam("imagefile") MultipartFile file, // changed from 'image'
#RequestParam("unitid") long id) throws IOException {
long myid=id;
recipeRepository.save(recipe);
long ids=recipe.getId();
Recipe u=recipeRepository.findById(ids);
model.addAttribute("addingredient",recipe);
UnitOfMeasure ob=unitOfMeasureRepository.findById(myid);
Byte[] byteObjects = convertToBytes(file); // we have to convert it to Byte[] array
u.setImage(byteObjects);
recipeRepository.save(u); // TODO refactor - save once
ingredient.setRecipe(u);
ingredient.setUnitOfMeasure(ob);
ingredientRepository.save(ingredient);
return "redirect:/recipe/insert_recipe";
}
private Byte[] convertToBytes(MultipartFile file) throws IOException {
Byte[] byteObjects = new Byte[file.getBytes().length];
int i = 0;
for (byte b : file.getBytes()) {
byteObjects[i++] = b;
}
return byteObjects;
}
Additional remarks:
Have a look at how Sfg handles the image upload and the displaying of it in the tutorial repository
It would be better to move the MultiPartFile to Byte[] conversion to the separate service (see the Sfg's repo / tutorial)
Edit:
Answering the question from the comment:
I do not use xampp.
The .bin extension suggests it is a binary file (makes sense as the image file is stored as the byte array).
Below is the snippet which should let you display the image in the browser instead.
IOUtils is from (import org.apache.tomcat.util.http.fileupload.IOUtils;)
#GetMapping("{id}/recipeimage")
public void renderImageFromDb(#PathVariable Long id, HttpServletResponse response) throws IOException {
Recipe recipe = recipeRepository.findById(id).get();
byte[] byteArray = new byte[recipe.getImage().length];
int i = 0;
for (Byte wrappedByte: recipe.getImage()) {
byteArray[i++] = wrappedByte; // auto unboxing
}
response.setContentType("image/jpeg");
InputStream is = new ByteArrayInputStream(byteArray);
IOUtils.copy(is, response.getOutputStream());
}
If you know the id of the recipe, just type localhost:8080/recipe/<recipe id>/recipeimage
Concerning your problem that the input is not bound to the ModelAttribute:
change th:name to name on your input field.
Concerning your type error:
Maybe this could help you: Upload files to the #ModelAttribute using Thymeleaf
You need to use the correct type which is MultipartFile for your image. Consider using another class called e.g. RecipeDto on your Controllers method signature. Map this to your Recipe Entity so you can somehow convert the MultipartFile to a Byte array manually.
edit: org.springframework.web.multipart.MultipartFile#getBytes might do this for you
Concerning DTO:
What are the DAO, DTO and Service layers in Spring Framework?
https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
i've a spring MVC application and when i try to upload a multipart file, a null value is passed to the controller. all the other text parameters are passed properly only the file input is passed as null. I've included the MultipartResolver bean and commons-io plus commons-fileupload dependencies in my project. i've checked it's passed in the browser's request but it's not bound in the modelAttribute.
here is a snippet code from my view
<form:form method="post" enctype="multipart/form-data" action="${pageContext.request.contextPath}/profile/secure/saveIdentity" commandName="profileModel">
<span><b>Upload your passport photo</b></span>
<form:input path="passportPhotograph" type="file" id="passportPhoto"/>
</form:form>
and here is a snippet from my controller method
#RequestMapping(value = "/secure/saveIdentity", method = RequestMethod.POST)
public ModelAndView saveIdentity(#ModelAttribute("profileModel") #Valid ProfileModel profileModel,HttpServletRequest request){
MultipartFile photo = profileModel.getPassportPhotograph();
if(photo != null){ do something.... }
}
here is my ProfileModel.java class snippet
public class ProfileModel{
private MultipartFile passportPhotograph;
public MultipartFile getPassportPhotograph() {
return passportPhotograph;
}
public void setPassportPhotograph(MultipartFile passportPhotograph) {
this.passportPhotograph = passportPhotograph;
}
............
}
And in my dispatcher-servlet file I have declared MultipartResolver bean:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="99999999999"/>
</bean>
and finally in my build.gradle file i added these dependencies
compile('commons-io:commons-io:2.0.1')
compile('commons-fileupload:commons-fileupload:1.3.1')
after all this it's passing null to my controller even though it's included in the HttpServletRequest. what should i do to fix this. Thanks in advance for you help.
you need to use #RequestMapping(value = "/secure/saveIdentity", method = RequestMethod.POST, headers = ("content-type=multipart/*"), produces = "application/json", consumes = "image/*").
Solved my issue by using the StandardServletMultipartResolver which comes with the spring web framework. But you have to use Servlet version 3.0+ for this to work.
This sample code. point is input tag name binding file in model. maybe you need library json and file. ex) commons-io, commons-fileupload and jackson, gson use to parsing in controller.
HTML
<form:form method="post" enctype="multipart/form-data" action="${pageContext.request.contextPath}/profile/secure/saveIdentity">
<span><b>Upload your passport photo</b></span>
<form:input type="file" name="file"/>
</form:form>
Controller
#RequestMapping(value = "test", method = RequestMethod.POST)
public String testFormData(FileAndContentModel model) {
// break point and check model.
return "success";
}
Model
public class FileAndContentModel {
private MultipartFile file;
public FileAndContentModel () {
}
// getter, setter
}
This is not the solution, I just type here because is easier to read than a comment. Please try the minimum requeriment, just upload a file and verify if works.
Form:
<form:form method="post" enctype="multipart/form-data" action="${pageContext.request.contextPath}/profile/secure/saveIdentity">
<span><b>Upload your passport photo</b></span>
<input name="file" type="file" id="passportPhoto"/>
</form:form>
Controller:
#RequestMapping(value = "/secure/saveIdentity", method = RequestMethod.POST)
public void saveIdentity(#RequestParam("file") MultipartFile file) {
//please verify here if the file is null
I am considerably new with Spring, and I want to have a multipart form and handle the MaxUploadSizeExceededException exception in order to show an error message in the jsp. The main problem I have is the ModelAndView Object in the class MaxUploadSizeLimitExceededExceptionResolver, that I don't know how to use it for such objective previously explained.
Files that I have:
1) Model class UploadItem.java. 2) View form UploadForm.jsp. 3) Controller Uploadcontroller.java. 4) Class MaxUploadSizeLimitExceededExceptionResolver.java to handle the exception Uploadcontroller
1) Model UploadItem
public class UploadItem {
private String name;
private CommonsMultipartFile fileData;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CommonsMultipartFile getFileData() {
return fileData;
}
public void setFileData(CommonsMultipartFile fileData) {
this.fileData = fileData;
}
}
2) View form UploadForm.jsp
<html>
<head>
<META http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Upload form</title>
</head>
<body>
<form:form modelAttribute="uploadItem" method="post" enctype="multipart/form-data">
<fieldset>
<legend>Upload Fields</legend>
<p>
<form:label for="name" path="name">Name</form:label><br/>
<form:input path="name"/>
</p>
<p>
<form:label for="fileData" path="fileData">File</form:label><br/>
<form:input path="fileData" type="file"/>
</p>
<p>
<input type="submit" />
</p>
</fieldset>
</form:form>
</body>
</html>
3) Controller Uploadcontroller
public class Uploadcontroller {
#RequestMapping(method = RequestMethod.GET)
public String getUploadForm(Model model) {
model.addAttribute(new UploadItem());
return "upload/uploadForm";
}
#RequestMapping(method = RequestMethod.POST)
public String create(HttpServletRequest request, UploadItem uploadItem,
BindingResult result, Exception exception) {
if (result.hasErrors()) {
// logger.info("create upload");
for (ObjectError error : result.getAllErrors()) {
System.err.println("Error: " + error.getCode() + " - "
+ error.getDefaultMessage());
}
return "upload/uploadForm";
}
System.err.println("Test upload: " + uploadItem.getName());
System.err.println("Test upload: "
+ uploadItem.getFileData().getOriginalFilename());
// TODO redirect this
return "/home";
}
}
4) Component to handle the exception Uploadcontroller
#Component
public class MaxUploadSizeLimitExceededExceptionResolver extends
SimpleMappingExceptionResolver {
private static final Logger logger = LoggerFactory
.getLogger(HomeController.class);
#Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception exception) {
Map<String, Object> model = new HashMap<String, Object>();
logger.info("getUploadForm at the beggining");
ModelAndView modelView = new ModelAndView();
if (exception instanceof MaxUploadSizeExceededException)
{
model.put("errors", exception.getMessage());
modelView.addObject("errors", exception.getMessage());
logger.info("getUploadForm: MaxUploadSizeExceededException" );
} else
{
model.put("errors", "Unexpected error: " + exception.getMessage());
modelView.addObject("errors", "Unexpected error: " +exception.getMessage());
}
logger.info("getUploadForm at the end" );
model.put("uploadedFile", new UploadItem());
modelView.addObject(new UploadItem());//("uploadedFile", new UploadItem());
modelView.setViewName("/upload/uploadForm");
return modelView;
}
}
Edit to add more details:
Actually, the problem is in another direction. The size of my maxUploadSize file was set to 1MB, but I was trying to do tests with files bigger than 3 MB. When I try with a file until 2 MB maximum is working fine. The problem is that I get a ERR_CONNECTION_RESET and it seems something related to Tomcat, a config of maxSwallowSize --> stackoverflow.com/questions/29113262/… I will continue researching and keep this thread updated.
New information.
I have tried with Tomcat 7.0.61 and the error is ERR_CONNECTION_ABORTED
I have tried with Tomcat 6.0.43 and there is no error!
After investigating, the problem was due to Tomcat.In the version 7.0.55 was introduced the property maxSwallowSize set by default to 2MB. This made Tomcat to abort the upload request. By setting this attribute to another value the problem is solved (I changed it to -1, please do not do that in your PRD environment as Tomcat will really try to get a X MB file to upload). I did this by adding in my Tomcat server file ${tomcat_dir}/conf/server.xml in my connector property the attribute maxSwallowSize
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxSwallowSize="-1"/>
You need to restart Tomcat so it takes this configuration, if does not work, delete the server and add it again.
I'm using spring mvc. And I can't get param from url when method = post. But when I change method to GET, so I can get all param.
This is my form:
<form method="POST" action="http://localhost:8080/cms/customer/create_customer" id="frmRegister" name ="frmRegister" enctype="multipart/form-data">
<input class ="iptRegister" type="text" id="txtEmail" name="txtEmail" value="" />
<input class ="iptRegister" type="password" id="txtPassword" name="txtPassword" value="" />
<input class ="iptRegister" type="text" id="txtPhone" name="txtPhone" value="" />
<input type="button" id="btnRegister" name="btnRegister" value="Register" onclick="" style="cursor:pointer"/>
</form>
This is my controller:
#RequestMapping(value= "/create_customer", method = RequestMethod.POST)
#ResponseBody
public String createCustomer(HttpServletRequest request,
#RequestParam(value="txtEmail", required=false) String email,
#RequestParam(value="txtPassword", required=false) String password,
#RequestParam(value="txtPhone", required=false) String phone){
ResultDTO<String> rs = new ResultDTO<String>();
rs.setStatus(IConfig.SHOW_RESULT_SUCCESS_ON_MAIN_SCREEN);
try{
Customer c = new Customer();
c.setEmail(email);
c.setPassword(password);
c.setPhone(phone);
customerService.insert(c);
rs.setData("Insert success");
}catch(Exception ex){
log.error(ex);
rs.setStatus(IConfig.SHOW_RESULT_ERROR_ON_MAIN_SCREEN);
rs.setData("Insert failure");
}
return rs.toString();
}
How can I resolved this?
Spring annotations will work fine if you remove enctype="multipart/form-data".
#RequestParam(value="txtEmail", required=false)
You can even get the parameters from the request object .
request.getParameter(paramName);
Use a form in case the number of attributes are large. It will be convenient. Tutorial to get you started.
Configure the Multi-part resolver if you want to receive enctype="multipart/form-data".
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="250000"/>
</bean>
Refer the Spring documentation.
It also works if you change the content type
<form method="POST"
action="http://localhost:8080/cms/customer/create_customer"
id="frmRegister" name="frmRegister"
enctype="application/x-www-form-urlencoded">
In the controller also add the header value as follows:
#RequestMapping(value = "/create_customer", method = RequestMethod.POST, headers = "Content-Type=application/x-www-form-urlencoded")
When I want to get all the POST params I am using the code below,
#RequestMapping(value = "/", method = RequestMethod.POST)
public ViewForResponseClass update(#RequestBody AClass anObject) {
// Source..
}
I am using the #RequestBody annotation for post/put/delete http requests instead of the #RequestParam which reads the GET parameters.
You should use #RequestParam on those resources with method = RequestMethod.GET
In order to post parameters, you must send them as the request body. A body like JSON or another data representation would depending on your implementation (I mean, consume and produce MediaType).
Typically, multipart/form-data is used to upload files.
I was using JSR 303 validation with hibernate validator to have fields validated automatically by specifying #Valid on the controller method. Validation was working fine. I have know added an upload field to the form and have added a #RequestParam("file") as a MultipartFile file. Now it works only if all fields are valid on submission otherwise I get a 404 (Bad Request). If I remove the #Valid annotation I get the javax.validation.ConstraintViolationException with all the validation violations with Status 500.
I'm using Spring 3.2
my form:
<form action="#springUrl("/admin/stores/save")" method="POST" enctype="multipart/form-data">
Name:
#springBind( "store.name" )
<input type="text"
name="${status.expression}"
value="$!status.value" /><br>
......
<input type="file" name="file" accept="image/*">
<input type="submit" value="submit"/>
</form>
Controller:
#RequestMapping(value="/save", method = RequestMethod.POST)
#Transactional
public String save(#Valid #ModelAttribute Store store, #RequestParam("file") MultipartFile file, BindingResult bindingResult, ModelMap model) {
if (bindingResult.hasErrors()) {
model.addAttribute("message", "Failed");
model.addAttribute("store", store);
return "admin/stores/form";
} else {
.....
your problem is in method argument order. #ModelAttribute must be followed by BindingResult argument. Look at Spring documentation and check also Example 17.1. Invalid ordering of BindingResult and #ModelAttribute.
. You also should add MultipartFile to form class (Store) if it is part of it.
Store {
/* Your other fields */
#NotNull
private MultipartFile file;
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file= file;
}
}