Upload files in Spring Boot
A web application may be required to give options to its users to upload different types of files. But some of these files may be sensitive and confidential (like the scanned copy of your passport or driving license) and should never be stored in plain. What if the server is breached? The intruder can access all the documents that are stored and use it as per their wish, possibly harming the owner of the document.
In this series of articles, I’ll be trying to create a basic application using Spring Boot to upload secured files on to the server, that can only be accessed from the application. Although I make no claims that this is the best possible method for file security, this should give a general idea on how things are done.
Note: This is the first article of a three-part series. In this article, I’ll be setting up the model, the api and service to upload documents. This article does not cover the security aspects, nor does it upload the actual file. That will be covered in the subsequent articles.
Articles in this series:
- Upload files in Spring Boot (this)
- Saving Files to System in Spring Boot
For experienced developers, some of the content might seem unnecessary. Please bare with me as I’m trying to explain things so that audiences of all level can understand this. Please feel free to skim over if things are too easy for you.
Alright. First of all, let’s create a new Spring Boot project. We’ll call it secretfile.
We won’t be using a lot of dependencies here. Since this is a web application, we’ll need to select the Spring Web Starter; we’ll use the Spring Data JPA to persist our data and we’ll use H2 Database as our underlying database. You could easily replace H2 with your favorite database (MySQL, PostgreSQL or others). To keep things simple and avoid details about database configuration, here we’ll be using H2.
Alright. Go ahead and click on finish. Your project is now ready to be run. Easy Peasy Japanesey.
Well, it runs but if you check localhost:8080
at this point, you’ll just get an error page, and that’s not very nice to see. Let’s add some meaningful code to it. Since our ultimate goal is to save files, let’s go ahead and add a model — Document that gives a blueprint for our files. First of all, let’s create a package for it and name it com.example.secretfile.model
. Create this under src/main/java
.
Next, we create a class in this package called Document.
We’ll just add a name field for now to give a name for our document. Add the getters and setters (could easily be generated from STS). Yes, you could use Lombok to handle those, but let’s keep things simple. Finally, we need to add @Entity
annotation to this class to let Spring know that we want to persist this class into a table. Our new class Document now looks like this.
Not much in it right now, but this works. Let’s go ahead and run our application.
Bummer! Our code doesn’t run. Let’s look why. The console log says no identifier specified for entity. Of course. Let’s add an identifier to our entity.
@Id
will tell Spring that this is the primary key. @GeneratedValue
defines how to increment this primary key for every row in the table. Read more about identifiers here. Finally, in @Column
, we define that we don’t want to update the primary key and we always want a value for it. Since this is auto-generated and we don’t want to update it, we just want a getter for the id
column.
Alright. Time for some action. Run the application and there should be no problem. But wait, how do we know the table is created or not? Where do we check that?
Open application.properties
under src/main/resources
and add the following code.
We now have our datasource url and driver classes defined. We also enabled h2 console and defined the path at /h2-console
. Great! Run the application and head over to localhost:8080/h2-console
. You should see the following in your browser.
Click on connect and there you have it.
The table is created and the columns are defined. But this empty table doesn’t look good. Let’s add some data to it. To do that, we would need to expose an api to add documents. But before that, let’s add some more columns to this table, so that we have more information about the documents we add.
First is mimeType, which represents the type of the file that we upload (jpeg, png, pdf, etc.). Next we have size, which gives the size in bytes. Finally, there’s hash. We’ll see why we need hash later on. For now, let’s just say it is a unique identifier for each document. So, if you upload the same document twice, they may have the same name, but they will have different hash. Why? The answer is in the setter method setHash
. It takes the name
, mimeType
and size
of the document, plus the current time. If a document is uploaded twice, the name
, mimeType
and the size
are the same, but the time differs so the transformedName
becomes different. Next, the transformedName
is hashed using MD5. You can read more about MD5 and hashing here.
Our model is now ready. Now, let’s define that REST api that will enable us to add the documents. Create DocumentController.java
under a new package com.example.secretfile.controller
and add the following code.
We have a POST method that consumes multipart/form-data. The parameter contains multipartFiles that will be used by the service — DocumentService. We have injected this service using @Autowired
but we have not yet defined it. Let’s do that next.
Create an interface DocumentService.java
under com.example.secretfile.service
. Define a method addDocuments
that accepts an array of MultipartFile.
But what good is an interface without an implementation? Create DocumentServiceImpl.java
under com.example.secretfile.service.impl
.
The implementation of addDocuments
doesn’t do much. It just loops through the array and calls the create
method on each MultipartFile. The create
method does all the work of creating a new document and setting the details. The setHash
method from our model generates the hash for this document. Finally, the document is saved. Once again, we have autowired DocumentRepository
which we will define next.
This repository extends the JpaRepository
which provides us with the save
method that we used in the service implementation. Add the required exceptions in the service interface and the controller, and we’re good to go.
Run the application. Open up an api client like Postman and then send a POST request to the api we just created.
Remember we named our request param as documents
in the controller? That’s what we need to send now. Add as many files as you like, just make sure that all of the keys are named documents and the type chosen is File. Here, I’ve chosen two files — a jpeg and a csv. POSTing this gives us the 200 OK status, but there is no response because our return type was void.
Go the H2 console and check.
And we’ll see that our documents were successfully added. Nice work. But wait, these are just the information about the files. Where’s the actual physical file? It’s nowhere. Because we never really uploaded it in our file system. And that’s what we need to do next. But this article is getting too long, so we’ll continue this in Part 2. You can find the complete code in the link below: