Decompress Brotli Requests in Spring Boot

How to Decompress Brotli Requests in Spring Boot with Brotli4j

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.xml

This 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.java

This 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.java

Explanation:

  • 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.java

Explanation:

  • 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 the Decoder.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.java

Explanation:

  • 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 the Employee 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 the Employee 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.java

5. 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.json

Now, compress this file using Brotli:

brotli < data.json > output.br
cmd or terminal


Step-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 to br. Also, set the Content-Type to application/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?

Can I use Brotli for both request decompression and response compression in Spring Boot?

What if I send an uncompressed request but include the Content-Encoding: br header?

Can I combine Brotli decompression with other compression algorithms?



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

Your email address will not be published.