Learn how to decompress Brotli requests in Spring Boot using Brotli4j easily. This beginner-friendly guide explains how to handle Brotli-encoded requests with a filter-based approach, including full code examples and testing steps.
1. Introduction
In our previous article, How to Use Brotli Compression in Spring Boot with Brotli4j, we covered how to compress responses in Spring Boot using Brotli. Compression is crucial for reducing response sizes, leading to faster load times and bandwidth savings. However, alongside compression, it’s equally important to handle decompression when a client sends Brotli-compressed requests.
In this article, we’ll walk through the entire process of setting up Brotli request decompression using a filter-based approach. By the end, you’ll know how to handle Brotli compressed requests efficiently.
2. Why Decompress Brotli Requests?
With the growing popularity of Brotli compression, many clients or browsers may send Brotli-compressed requests to your server, especially for large or repeated data such as file uploads or JSON payloads. Decompressing these requests ensures that your Spring Boot application can properly interpret and process the data sent by the client.
Without the ability to handle Brotli-compressed requests, you may risk losing performance gains, or worse, fail to parse incoming requests altogether.
3. Does Spring Boot Offer Built-In Brotli Decompression?
No, Spring Boot doesn’t offer out-of-the-box Brotli decompression. However, using Brotli4j, a popular Java library for Brotli compression and decompression, we can easily integrate Brotli decompression into our Spring Boot applications.
4. Decompress Brotli Requests in Spring Boot
To implement Brotli decompression in the Spring Boot application, we’ll use Brotli4j to decompress the request body. The steps below will guide you through adding the Brotli4j dependency, setting up the filter to intercept and decompress Brotli-encoded requests, and creating a simple controller for testing.
Step 1: Add the Brotli4j Maven Dependency
First, you need to add the brotli4j dependency to your pom.xml
file. The latest version of the dependency can be obtained from here.
<dependency>
<groupId>com.aayushatharva.brotli4j</groupId>
<artifactId>brotli4j</artifactId>
<version>1.17.0</version>
</dependency>
pom.xmlThis library provides the necessary utilities to decompress data using Brotli.
Step 2: Load Brotli4j in the Startup Class
In your main application class, ensure that the Brotli4j native library is loaded. You can do this by using the Brotli4jLoader.ensureAvailability()
method in the CommandLineRunner
interface.
import com.aayushatharva.brotli4j.Brotli4jLoader;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootDecompressBrotliRequestsApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringBootDecompressBrotliRequestsApplication.class, args);
}
@Override
public void run(String... args) {
// Load the native library
Brotli4jLoader.ensureAvailability();
}
}
SpringBootDecompressBrotliRequestsApplication.javaThis ensures that Brotli4j is ready to be used for Brotli decompression.
Step 3: Create the Brotli Decompression Filter
Next, create a filter that intercepts every incoming HTTP request and checks if it is Brotli-compressed. If the request has the Content-Encoding
header set to “br” (indicating Brotli compression), the filter will pass the request to a wrapper that will handle decompression.
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Order(1)
@Component
public class BrotliDecompressionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String contentEncoding = request.getHeader("Content-Encoding");
if (contentEncoding != null && contentEncoding.toLowerCase().contains("br")) {
HttpServletRequest wrappedRequest = new BrotliWrappedRequest(request);
filterChain.doFilter(wrappedRequest, response);
} else {
filterChain.doFilter(request, response);
}
}
}
BrotliDecompressionFilter.javaExplanation:
- doFilterInternal(): This method checks if the request is Brotli-compressed by inspecting the
Content-Encoding
header. If the header contains"br"
, it means the request is Brotli-encoded, and we pass the request to a wrapper for decompression. - filterChain.doFilter(): The
filterChain
continues processing the request, either the original or the wrapped one (if Brotli encoding is detected).
Step 4: Create a Custom HTTP Servlet Request Wrapper
Next, create a customer HTTP servlet request wrapper class. This is where the actual decompression happens. It wraps the original HttpServletRequest
and overrides its methods to provide the decompressed data in place of the Brotli-encoded data. The main role of this class is to intercept the request’s input stream (which contains compressed data) and replace it with a decompressed version.
import com.aayushatharva.brotli4j.decoder.Decoder;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
class BrotliWrappedRequest extends HttpServletRequestWrapper {
private final byte[] decompressedBody;
public BrotliWrappedRequest(HttpServletRequest request) throws IOException {
super(request);
decompressedBody = Decoder.decompress(request.getInputStream().readAllBytes())
.getDecompressedData();
}
@Override
public ServletInputStream getInputStream() {
return new ServletInputStream() {
private final ByteArrayInputStream inputStream = new ByteArrayInputStream(decompressedBody);
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public boolean isFinished() {
return inputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
throw new UnsupportedOperationException();
}
};
}
}
BrotliWrappedRequest.javaExplanation:
- HttpServletRequestWrapper: We extend
HttpServletRequestWrapper
to modify how the request body is handled. Specifically, we need to override how the input stream behaves since the original data is Brotli-compressed. - decompressedBody: This variable stores the decompressed data after Brotli4j has processed the compressed input. We read the compressed data from the original request’s input stream using
request.getInputStream().readAllBytes()
, and then pass it to theDecoder.decompress()
method from Brotli4j to decompress it. - getInputStream(): This is the most important method we override. The original request contains a compressed input stream. By overriding this method, we provide the decompressed data to the Spring application. Instead of the compressed stream, the controller receives the decompressed version, which is easier for Spring Boot to process.
The result is that when Spring Boot reads the input stream, it sees the decompressed data instead of the compressed Brotli content. This wrapper is essential for handling Brotli-compressed requests transparently, without requiring changes in how the controller processes the request body.
Step 5: Create a Simple Controller for Testing
Now, create a simple controller to test the decompression. This controller processes the decompressed request body. In our example, we receive an Employee
object, which was originally compressed in the request but has been decompressed by the filter.
import com.bootcamptoprod.dto.Employee;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BrotliDecompressController {
@PostMapping(value = "/decompress")
public ResponseEntity<Employee> decompress(@RequestBody Employee employee) {
System.out.println("Employee Id: " + employee.getId());
System.out.println("Employee Name: " + employee.getName());
return ResponseEntity.ok(employee);
}
}
BrotliDecompressController.javaExplanation:
- decompress(): This method is annotated with
@PostMapping
, meaning it handles POST requests sent to/decompress
. The@RequestBody
annotation tells Spring Boot to map the request body to theEmployee
object. Thanks to our filter and wrapper, the request body is already decompressed by the time it reaches this method, so we can work with theEmployee
object as usual.
Employee Model
This class represents the data structure that we expect in the request body. This class has two fields: id
and name
, representing an employee’s basic information.
public class Employee {
private int id;
private String name;
// Getters and setters
}
Employee.java5. Testing Brotli Decompression
Let’s test our implementation by sending a Brotli-compressed request to our Spring Boot application.
Step-1: Install Brotli
To test this, you’ll need Brotli installed on your machine:
- For Ubuntu:
sudo apt install brotli
- For macOS:
brew install brotli
- For Windows:
choco install brotli
Step-2: Create a Brotli-Compressed File
First, create a JSON file named data.json
with the following content:
{
"id": 1,
"name": "John Doe"
}
data.jsonNow, compress this file using Brotli:
brotli < data.json > output.br
cmd or terminalStep-3: Send the Brotli-Compressed Request Using Postman
- Open Postman and create a new POST request to
http://localhost:8080/decompress
. - In the Headers section, set the
Content-Encoding
header tobr
. Also, set theContent-Type
toapplication/json
. - In the Body section, choose binary and upload the
output.br
file you just created. - Click Send.
Step-4: Verify the Output
If everything is set up correctly, you should see the server output the decompressed employee data in the console, indicating that the Brotli decompression was successful. Postman should also return the decompressed JSON response with the same employee data:
This confirms that the Spring Boot application successfully received and processed the Brotli-compressed request by decompressing it via the custom filter and wrapper we implemented.
6. Source Code
The complete source code of the above example can be found here.
7. Things to Consider
Here are some important considerations to keep in mind while working with Brotli decompression in Spring Boot:
- Handling Large Payloads: Brotli compression is effective for reducing payload sizes, but decompressing large payloads can increase memory usage. Be mindful of memory management when dealing with large compressed requests.
- Filter Placement: Ensure that the Brotli decompression filter is applied early in the filter chain to guarantee that the incoming compressed requests are properly decompressed before any other processing happens.
- Exception Handling: Implement robust exception handling to manage cases where decompression fails, either due to corrupted Brotli data or unexpected content. Return meaningful error responses to the client in such scenarios.
- Performance Impact: Brotli decompression can introduce some CPU overhead. Test your application under load to assess any potential performance impact, especially when dealing with high-traffic environments or large request bodies.
- Testing in Real Environments: Always test Brotli decompression in real environments, including production-like setups, to ensure the functionality works as expected with actual clients and networks.
- Content-Type Compatibility: Brotli compression is typically used with certain content types like JSON or HTML. Ensure the correct
Content-Type
headers are maintained and that the decompressed data is compatible with your application’s expected input format.
8. FAQs
How can I limit the size of Brotli-compressed requests?
You can configure your Spring Boot application to reject requests exceeding a certain size by using filters or request size limits. This can help prevent excessive memory usage or potential attacks involving large payloads.
Can I use Brotli for both request decompression and response compression in Spring Boot?
Yes, you can implement Brotli for both decompression of incoming requests and compression of outgoing responses. Each direction will require its own filter to handle the respective operation.
What if I send an uncompressed request but include the Content-Encoding: br header?
If you send an uncompressed request with Content-Encoding: br, the server will attempt decompression and likely fail. Proper error handling should be added to catch such cases and return a clear error response.
Can I combine Brotli decompression with other compression algorithms?
Yes, but you need to handle them separately based on the Content-Encoding header. Each request should be decompressed according to the specified encoding.
9. Conclusion
Decompressing Brotli requests in Spring Boot is a crucial feature when dealing with modern clients that send compressed data. By using the Brotli4j library and configuring a simple filter, you can easily handle Brotli-compressed requests. With this setup, your Spring Boot application will be ready to receive and process Brotli-compressed data, enhancing performance and compatibility.
10. Learn More
Interested in learning more?
Checkout our blog on How to make parallel calls using RestTemplate and CompletableFuture
Add a Comment