Recommended way to save uploaded files in a servlet application
Solution 1
Store it anywhere in an accessible location except of the IDE's project folder aka the server's deploy folder, for reasons mentioned in the answer to Uploaded image only available after refreshing the page:
Changes in the IDE's project folder does not immediately get reflected in the server's work folder. There's kind of a background job in the IDE which takes care that the server's work folder get synced with last updates (this is in IDE terms called "publishing"). This is the main cause of the problem you're seeing.
In real world code there are circumstances where storing uploaded files in the webapp's deploy folder will not work at all. Some servers do (either by default or by configuration) not expand the deployed WAR file into the local disk file system, but instead fully in the memory. You can't create new files in the memory without basically editing the deployed WAR file and redeploying it.
Even when the server expands the deployed WAR file into the local disk file system, all newly created files will get lost on a redeploy or even a simple restart, simply because those new files are not part of the original WAR file.
It really doesn't matter to me or anyone else where exactly on the local disk file system it will be saved, as long as you do not ever use getRealPath()
method. Using that method is in any case alarming.
The path to the storage location can in turn be definied in many ways. You have to do it all by yourself. Perhaps this is where your confusion is caused because you somehow expected that the server does that all automagically. Please note that @MultipartConfig(location)
does not specify the final upload destination, but the temporary storage location for the case file size exceeds memory storage threshold.
So, the path to the final storage location can be definied in either of the following ways:
-
Hardcoded:
File uploads = new File("/path/to/uploads");
-
Environment variable via
SET UPLOAD_LOCATION=/path/to/uploads
:File uploads = new File(System.getenv("UPLOAD_LOCATION"));
-
VM argument during server startup via
-Dupload.location="/path/to/uploads"
:File uploads = new File(System.getProperty("upload.location"));
-
*.properties
file entry asupload.location=/path/to/uploads
:File uploads = new File(properties.getProperty("upload.location"));
-
web.xml
<context-param>
with nameupload.location
and value/path/to/uploads
:File uploads = new File(getServletContext().getInitParameter("upload.location"));
-
If any, use the server-provided location, e.g. in JBoss AS/WildFly:
File uploads = new File(System.getProperty("jboss.server.data.dir"), "uploads");
Either way, you can easily reference and save the file as follows:
File file = new File(uploads, "somefilename.ext");
try (InputStream input = part.getInputStream()) {
Files.copy(input, file.toPath());
}
Or, when you want to autogenerate an unique file name to prevent users from overwriting existing files with coincidentally the same name:
File file = File.createTempFile("somefilename-", ".ext", uploads);
try (InputStream input = part.getInputStream()) {
Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
How to obtain part
in JSP/Servlet is answered in How to upload files to server using JSP/Servlet? and how to obtain part
in JSF is answered in How to upload file using JSF 2.2 <h:inputFile>? Where is the saved File?
Note: do not use Part#write()
as it interprets the path relative to the temporary storage location defined in @MultipartConfig(location)
. Also make absolutely sure that you aren't corrupting binary files such as PDF files or image files by converting bytes to characters during reading/writing by incorrectly using a Reader
/Writer
instead of InputStream
/OutputStream
.
See also:
- How to save uploaded file in JSF (JSF-targeted, but the principle is pretty much the same)
- Simplest way to serve static data from outside the application server in a Java web application (in case you want to serve it back)
- How to save generated file temporarily in servlet based web application
Solution 2
I post my final way of doing it based on the accepted answer:
@SuppressWarnings("serial")
@WebServlet("/")
@MultipartConfig
public final class DataCollectionServlet extends Controller {
private static final String UPLOAD_LOCATION_PROPERTY_KEY="upload.location";
private String uploadsDirName;
@Override
public void init() throws ServletException {
super.init();
uploadsDirName = property(UPLOAD_LOCATION_PROPERTY_KEY);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// ...
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Collection<Part> parts = req.getParts();
for (Part part : parts) {
File save = new File(uploadsDirName, getFilename(part) + "_"
+ System.currentTimeMillis());
final String absolutePath = save.getAbsolutePath();
log.debug(absolutePath);
part.write(absolutePath);
sc.getRequestDispatcher(DATA_COLLECTION_JSP).forward(req, resp);
}
}
// helpers
private static String getFilename(Part part) {
// courtesy of BalusC : http://stackoverflow.com/a/2424824/281545
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String filename = cd.substring(cd.indexOf('=') + 1).trim()
.replace("\"", "");
return filename.substring(filename.lastIndexOf('/') + 1)
.substring(filename.lastIndexOf('\\') + 1); // MSIE fix.
}
}
return null;
}
}
where :
@SuppressWarnings("serial")
class Controller extends HttpServlet {
static final String DATA_COLLECTION_JSP="/WEB-INF/jsp/data_collection.jsp";
static ServletContext sc;
Logger log;
// private
// "/WEB-INF/app.properties" also works...
private static final String PROPERTIES_PATH = "WEB-INF/app.properties";
private Properties properties;
@Override
public void init() throws ServletException {
super.init();
// synchronize !
if (sc == null) sc = getServletContext();
log = LoggerFactory.getLogger(this.getClass());
try {
loadProperties();
} catch (IOException e) {
throw new RuntimeException("Can't load properties file", e);
}
}
private void loadProperties() throws IOException {
try(InputStream is= sc.getResourceAsStream(PROPERTIES_PATH)) {
if (is == null)
throw new RuntimeException("Can't locate properties file");
properties = new Properties();
properties.load(is);
}
}
String property(final String key) {
return properties.getProperty(key);
}
}
and the /WEB-INF/app.properties :
upload.location=C:/_/
HTH and if you find a bug let me know
Mr_and_Mrs_D
Be warned - the Monster isAlife Git, Java, Android and finally Python I was flirting with JEE since a couple years but since 1/2014 we are having an affair I spent the best part of the last year refactoring a widely used mod manager application. Here is the commit message of the release I have been working on, where I detail what I have been doing: https://github.com/wrye-bash/wrye-bash/commit/1cd839fadbf4b7338b1c12457f601066b39d1929 I am interested in code quality and performance (aka in the code as opposed to what the code does) If you find my posts useful you can buy me a coffee TCP walks into a bar & says: “I’d like a beer.” “You’d like a beer?” “Yes, a beer.”
Updated on April 14, 2021Comments
-
Mr_and_Mrs_D about 3 years
I read here that one should not save the file in the server anyway as it is not portable, transactional and requires external parameters. However, given that I need a tmp solution for tomcat (7) and that I have (relative) control over the server machine I want to know :
What is the best place to save the file ? Should I save it in
/WEB-INF/uploads
(advised against here) or someplace under$CATALINA_BASE
(see here) or ... ? The JavaEE 6 tutorial gets the path from the user (:wtf:). NB : The file should not be downloadable by any means.Should I set up a config parameter as detailed here ? I'd appreciate some code (I'd rather give it a relative path - so it is at least Tomcat portable) -
Part.write()
looks promising - but apparently needs a absolute pathI'd be interested in an exposition of the disadvantages of this approach vs a database/JCR repository one
Unfortunately the FileServlet by @BalusC concentrates on downloading files, while his answer on uploading files skips the part on where to save the file.
A solution easily convertible to use a DB or a JCR implementation (like jackrabbit) would be preferable.
-
BalusC almost 11 yearsThe
@MultipartConfig(location)
specifies the temporary storge location which the server should use when the file size exceeds the threshold for memory storage, not the permanent storage location where you'd ultimately like it to be stored. This value defaults to the path as identified byjava.io.tmpdir
system property. See also this related answer to a failed JSF attempt: stackoverflow.com/questions/18478154/… -
Mr_and_Mrs_D almost 11 yearsThanks - hope I do not sound idiot but this quote from
Part.write
>> This allows a particular implementation to use, for example, file renaming, where possible, rather than copying all of the underlying data, thus gaining a significant performance benefit in conjunction with some unknown "cut" (vs copy) method from say some apache lib would save me the hassle of writing the bytes myself - and recreating a file already there (see also here) -
BalusC almost 11 yearsYes, if you're already on Servlet 3.0, you could make use of
Part#write()
. I updated the answer with it. -
Mr_and_Mrs_D almost 11 yearsThank you very much for keeping the post updated - is there such a property for Tomcat as
"jboss.server.data.dir"
? -
BalusC almost 11 yearsNope, it doesn't have.
-
Mr_and_Mrs_D almost 11 yearsFeel free to edit my updated answer (I posted my solution) with your insights :) Especially the
// "/WEB-INF/app.properties" also works...
is the only part that remains somewhat unclear -
BalusC almost 11 years
-
Mr_and_Mrs_D over 10 yearsglassfish does not support absolute file paths in
Part.write()
apparently : see here -
Marcel Korpel almost 9 yearsNote that when you change the
new File
part toFile.createTempFile(…)
you should also use theStandardCopyOption.REPLACE_EXISTING
flag toFiles.copy
, otherwise you'll get an error that the file already exists. -
pikimota almost 6 yearsWhat if I want a SO independent solution, that works in both (win/ux) case? Do I have to set different upload.location path or there is some other hint?