In the world of web applications, the ability to send and receive files is crucial. It allows users to upload various types of files, such as photos and documents, and enables applications to serve these files to other users or systems. Spring Boot provides robust capabilities for managing file transfers.
Sending files
Spring Boot provides versatile methods for sending files from your application to a client using the @RestController. Let's dive into the process.
To send a file, you need to locate it in your system first. You can do this using the java.nio.file.Path class. For example, if you have a file named example.txt in a directory called files, you could locate it like this:
Java
Path path = Path.of("files", "example.txt");Kotlin
val path: Path = Path.of("files", "example.txt")When you have the Path to the file, you can change it into a Resource using Spring's Resource and PathResource classes:
Java
Resource file = new PathResource(path);Kotlin
val file: Resource = PathResource(path)The Resource interface in Spring represents a handle connected to a resource–in this case, a file. It provides a simple and consistent way to access low-level resources. A complete example of a REST controller might look like:
Java
import org.springframework.core.io.PathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.nio.file.Path;
@RestController
class FileController {
@GetMapping(path = "/download")
public ResponseEntity<Resource> download() {
Path path = Path.of("files", "example.txt");
Resource file = new PathResource(path);
return ResponseEntity
.ok()
.contentType(MediaType.TEXT_PLAIN)
.header("Content-Disposition", "attachment; filename=" + file.getFilename())
.body(file);
}
}Kotlin
import org.springframework.core.io.PathResource
import org.springframework.core.io.Resource
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import java.nio.file.Path
@RestController
class FileController {
@GetMapping("/download")
fun download(): ResponseEntity<Resource> {
val path: Path = Path.of("files", "example.txt")
val file: Resource = PathResource(path)
return ResponseEntity
.ok()
.contentType(MediaType.TEXT_PLAIN)
.header("Content-Disposition", "attachment; filename=" + file.filename)
.body(file)
}
}In this code snippet, the download() method locates and loads a file as a Resource. This Resource gets then sent to the client within the HTTP response body. We expressly specify the content type as plain text and add the Content-Disposition header to notify the browser about the name of the file and, in this case, to force the browser to download the file. If we specified the Content-Disposition as inline, the browser would attempt to display the file content within the browser window.
However, the download() method isn't limited to returning ResponseEntity<Resource>. It can return any datatype that represents a file. For instance, you could return a byte[] to represent a binary file, or a String to represent a text file.
The major idea is to utilize the suitable datatype that fits your use case and the type of file you are dealing with. For example, if you're dealing with an image file, it may be more convenient to use byte[] or Resource, but if you're dealing with a CSV file, String could be a more apt choice.
Remember, the goal is to enable clients to download or view the file. Your choice of the return type should align with that objective.
Sending files asynchronously
There are scenarios where we need to send large files or generate copious amounts of data dynamically. In these cases, Spring provides the StreamingResponseBody interface which gives the ability to stream data straight to the HTTP response body.
The StreamingResponseBody interface has a single method void writeTo(OutputStream outputStream) throws IOException, which is responsible for writing data to the response output stream. This method is called by Spring MVC when processing the request.
By implementing the writeTo method, you have full control over how data is written to the response. Typically, you read the data from a source like a file, a database, or an in-memory stream and write it to the output stream in chunks. This approach permits the data to be streamed bit by bit, eradicating the need to load entire content into memory before sending it to the client.
Here is an example of how such streaming of data might look:
Java
import org.springframework.core.io.PathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.io.IOException;
import java.nio.file.Path;
@RestController
class FileController {
@GetMapping("/stream")
public ResponseEntity<StreamingResponseBody> stream() {
Path path = Path.of("data", "bigdata.zip");
// Creating a Resource from the path
Resource resource = new PathResource(path);
// Creating the StreamingResponseBody
StreamingResponseBody responseBody = outputStream -> {
try (InputStream inputStream = resource.getInputStream()) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
// handle the exception here
}
};
// Setting the response headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
try {
headers.setContentLength(resource.contentLength());
} catch (IOException e) {
// handle the exception
}
headers.setContentDispositionFormData("attachment", resource.getFilename());
// Assembling the response
return ResponseEntity
.ok()
.headers(headers)
.body(responseBody);
}
}Kotlin
import org.springframework.core.io.PathResource
import org.springframework.core.io.Resource
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
import java.io.IOException
import java.nio.file.Path
@RestController
class FileController {
@GetMapping("/stream")
fun stream(): ResponseEntity<StreamingResponseBody> {
val path: Path = Path.of("data", "bigdata.zip")
// Creating a Resource from the path
val resource: Resource = PathResource(path)
// Creating the StreamingResponseBody
val responseBody = StreamingResponseBody { outputStream ->
try {
resource.inputStream.use { inputStream ->
val buffer = ByteArray(4096)
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
}
} catch (e: IOException) {
// handle the exception here
}
}
// Setting the response headers
val headers = HttpHeaders()
headers.contentType = MediaType.APPLICATION_OCTET_STREAM
try {
headers.contentLength = resource.contentLength()
} catch (e: IOException) {
// handle the exception
}
headers.setContentDispositionFormData("attachment", resource.filename)
// Assembling the response
return ResponseEntity
.ok()
.headers(headers)
.body(responseBody)
}
}In this example, the /stream endpoint is in-charge of streaming the file to the client. Firstly, we create a Resource from the file we want to transfer. Then, we create an implementation of the StreamingResponseBody that reads the file in chunks and writes them to the output stream until the entire file has been dispatched. Afterwards, we set response headers, which includes the content type and content disposition, before sending the file. Finally, the response is delivered to the client.
Receiving multipart files
Now that you have had a look at sending files, let's delve into how Spring enables us to receive files from clients.
Clients frequently send files using a multipart request. This is a kind of HTTP request used when you want to relay multiple different sets of data together in the same request, like uploading files with other form data. The "multipart" attribute of the request refers to the notion that the request body is divided into different sections or "parts", each with its distinct headers and body.
The structure of a multipart request is determined by the MIME standard and uses the "multipart/form-data" media type.
Let's illustrate an example to upload files using a basic web form. First, we'll fabricate the web form and save it as the index.html file in the src/main/resources/static folder of the project:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File Uploader</title>
</head>
<body>
<h2>Upload File</h2>
<form action="/upload" method="post" enctype="multipart/form-data">
<label for="sender">Enter your name: </label>
<input type="text" id="sender" name="sender"><br>
<label for="file">Choose file: </label>
<input type="file" id="file" name="file">
<input type="submit">
</form>
</body>
</html>This form will dispatch the sender's name and the file to the server.
Next, we'll add a new handler method to the FileController class:
Java
@PostMapping("/upload")
public String upload(@RequestParam String sender, @RequestParam MultipartFile file) {
Path path = Path.of("uploads", file.getOriginalFilename());
try {
file.transferTo(path);
} catch (IOException e) {
// handle the exception
return e.getClass().getSimpleName() + ": Error uploading the file " + e.getMessage();
}
return file.getOriginalFilename() + " successfully uploaded by " + sender;
}Kotlin
@PostMapping("/upload")
fun upload(@RequestParam sender: String, @RequestParam file: MultipartFile): String {
val path: Path = Path.of("uploads", file.originalFilename)
try {
file.transferTo(path)
} catch (e: IOException) {
return e::class.java.simpleName + ": Error uploading the file " + e.message
}
return file.originalFilename + " successfully uploaded by " + sender
}The upload method handles POST requests and accepts two request parameters, String sender and MultipartFile file. The parameter names align with the names of the form fields from the HTML form shown above. The MultipartFile is an interface that represents a file received in a multipart HTTP request. It has various helpful methods to manage the contents of the file, which include methods to get the file's name, original filename, content type, and size.
The upload method fetches the original filename of the file and creates a path for saving it to the uploads folder. The file is replicated to the destination using the transferTo method of the MultipartFile interface. After that, a note about the successful upload is dispatched to the client.
This is not the sole way to process such files. You can apply any means provided by Java in java.io and java.nio packages.
It's worth mentioning that Spring applies default limits on the multipart file size which you can modify by adjusting different settings in the application.properties file, for example:
spring.servlet.multipart.max-file-size=2MB
spring.servlet.multipart.max-request-size=5MBReceiving files as request bodies
Clients can transmit files in binary format in request bodies as well. To process such data, you simply need to inject the request body as a byte array or InputStream and manage it accordingly:
Java
@PostMapping("/upload")
public String upload(@RequestBody byte[] bytes) {
Path path = Path.of("uploads", UUID.randomUUID().toString());
try {
Files.write(path, bytes);
} catch (IOException e) {
return "Error uploading file";
}
return "File successfully uploaded";
}Kotlin
@PostMapping("/upload")
fun upload(@RequestBody bytes: ByteArray): String {
val path: Path = Path.of("uploads", UUID.randomUUID().toString())
try {
Files.write(path, bytes)
} catch (e: IOException) {
return "Error uploading file"
}
return "File successfully uploaded"
}In this situation, the upload method injects the request body, generates a random name for the file to be saved, and scribes all bytes to the destination.
Conclusion
Spring provides flexible and user-friendly ways to send files to clients and receive files from clients of any size and in any format. We looked at the Resource interface that simplifies working with data sources, examined the issues of sending files to clients, and discussed the procedure of asynchronous transfer of large amounts of data. Additionally, we learned about multipart requests and the MultipartFile interface provided by Spring to manage files sent by clients as part of such requests. Lastly, we explored the potential of receiving binary data from request bodies.