Conditional Gzip Compression in Spring Boot

Conditional Gzip Compression in Spring Boot

Learn how to enable Gzip compression in Spring Boot based on specific conditions. Follow our detailed guide to optimize your application performance by configuring Gzip compression conditionally.

1. Introduction

In our previous article on Gzip compression in Spring Boot, we explained how to enable Gzip compression using simple properties in the application.properties file. This method, however, enables Gzip compression globally. But what if we want to enable Gzip compression based on certain conditions, such as when the “Accept-Encoding” header contains “gzip” or only for specific URLs?

In this article, we will explore three ways to achieve conditional Gzip compression in Spring Boot:

  • Configure exclude agents in application properties
  • Use the “Accept-Encoding” header with a value of “identity”
  • Implement a custom filter for Gzip compression


    2. Method 1: Configure Exclude Agents in Application Properties

    One way to control Gzip compression is by configuring excluded user agents in the application.properties file. This allows us to selectively disable Gzip compression based on the user agent making the request.

    Steps:

    1. Add excluded user agents in application.properties:

    server.compression.enabled=true
    server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,text/css,text/javascript,application/javascript
    server.compression.min-response-size=2048
    server.compression.excluded-user-agents=PostmanRuntime/7.39.0
    
    application.properties

    2. Use the specified user agent while making API calls to prevent Gzip compression:

    By configuring excluded agents, we can ensure that requests from specific user agents do not undergo Gzip compression, giving us finer control over which requests are compressed.



    3. Method 2: Use the Accept-Encoding Header with Value Identity

    Spring Boot enables Gzip compression globally when configured in application.properties. However, we can bypass this compression by using the “Accept-Encoding” header with a value of “identity”. The “identity” value indicates that no transformation or encoding should be applied to the response.

    Steps:

    1. Enable Gzip compression globally in application.properties:

    server.compression.enabled=true
    server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,text/css,text/javascript,application/javascript
    server.compression.min-response-size=2048
    
    application.properties

    2. Pass the “Accept-Encoding” header with value “identity” to skip Gzip compression:

    By using the “Accept-Encoding: identity” header, we instruct the server not to apply any content encoding, effectively disabling Gzip compression for that particular request.



    4. Method 3: Implement a Custom Filter for Conditional Gzip Compression

    For more granular control over Gzip compression, we can implement a custom filter that selectively applies Gzip compression based on specific conditions, such as the URL pattern or the presence of the “Accept-Encoding” header.

    Note: In this method, we will not enable Gzip compression globally via application.properties. Instead, the custom filter will independently handle the compression logic.

    Steps:

    1. create a custom filter class using OncePerRequestFilter:

    This ensures that the filter is only applied once per request.

    import jakarta.servlet.FilterChain;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import java.io.IOException;
    import java.util.List;
    
    @Component
    public class GzipResponseCompressionFilter extends OncePerRequestFilter {
    
        private static final List<String> EXCLUDE_GZIP_URLS = List.of("/exclude-compression");
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            if (acceptsGZipEncoding(request) && !EXCLUDE_GZIP_URLS.contains(request.getRequestURI())) {
                GzipHttpServletResponseWrapper gzipResponse = new GzipHttpServletResponseWrapper(response);
                gzipResponse.setHeader("Content-Encoding", "gzip");
                filterChain.doFilter(request, gzipResponse);
                gzipResponse.close();
            } else {
                filterChain.doFilter(request, response);
            }
        }
    
        private boolean acceptsGZipEncoding(HttpServletRequest request) {
            String acceptEncoding = request.getHeader("Accept-Encoding");
            return acceptEncoding != null && acceptEncoding.contains("gzip");
        }
    }
    
    GzipResponseCompressionFilter.java

    Explanation:

    • The GzipResponseCompressionFilter class extends OncePerRequestFilter, which ensures that the filter logic is executed only once per request.
    • The doFilterInternal method checks if the request supports Gzip encoding by examining the “Accept-Encoding” header and ensures the request URI is not in the EXCLUDE_GZIP_URLS list.
    • If the conditions are met, it wraps the response in a GzipHttpServletResponseWrapper to handle Gzip compression.
    • If the conditions are not met, it proceeds with the normal filter chain.

    2. Create a custom response wrapper class:

    This class wraps the HTTP response to add Gzip compression.

    import jakarta.servlet.ServletOutputStream;
    import jakarta.servlet.http.HttpServletResponse;
    import jakarta.servlet.http.HttpServletResponseWrapper;
    
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.io.PrintWriter;
    import java.util.zip.GZIPOutputStream;
    
    class GzipHttpServletResponseWrapper extends HttpServletResponseWrapper {
    
        private final GZIPOutputStream gzipOutputStream;
        private ServletOutputStream outputStream;
        private PrintWriter writer;
    
        public GzipHttpServletResponseWrapper(HttpServletResponse response) throws IOException {
            super(response);
            this.gzipOutputStream = new GZIPOutputStream(response.getOutputStream());
        }
    
        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            if (this.outputStream == null) {
                this.outputStream = new GzipServletOutputStream(this.gzipOutputStream);
            }
            return this.outputStream;
        }
    
        @Override
        public PrintWriter getWriter() throws IOException {
            if (this.writer == null) {
                this.writer = new PrintWriter(new OutputStreamWriter(this.gzipOutputStream, getCharacterEncoding()));
            }
            return this.writer;
        }
    
        @Override
        public void flushBuffer() throws IOException {
            if (this.writer != null) {
                this.writer.flush();
            }
            if (this.outputStream != null) {
                this.outputStream.flush();
            }
            this.gzipOutputStream.flush();
        }
    
        public void close() throws IOException {
            this.gzipOutputStream.close();
        }
    }
    
    GzipHttpServletResponseWrapper.java

    Explanation:

    • The GzipHttpServletResponseWrapper class extends HttpServletResponseWrapper to wrap the original response and add Gzip compression.
    • It overrides getOutputStream and getWriter to return Gzip-compressed output streams.The getOutputStream method returns a GzipServletOutputStream, which writes compressed data to the GZIPOutputStream.
    • The getWriter method returns a PrintWriter that writes to the GZIPOutputStream.
    • The flushBuffer method ensures all data is written to the client before the response is flushed.
    • The close method closes the GZIPOutputStream to complete the compression process.

    3. Create a custom servlet output stream class:

    This class handles writing the Gzip-compressed data to the output stream.

    import jakarta.servlet.ServletOutputStream;
    import jakarta.servlet.WriteListener;
    
    import java.io.IOException;
    import java.util.zip.GZIPOutputStream;
    
    class GzipServletOutputStream extends ServletOutputStream {
    
        private final GZIPOutputStream gzipOutputStream;
    
        public GzipServletOutputStream(GZIPOutputStream gzipOutputStream) {
            this.gzipOutputStream = gzipOutputStream;
        }
    
        @Override
        public boolean isReady() {
            return true;
        }
    
        @Override
        public void setWriteListener(WriteListener writeListener) {
        }
    
        @Override
        public void write(int b) throws IOException {
            this.gzipOutputStream.write(b);
        }
    }
    
    GzipServletOutputStream.java

    Explanation:

    • The GzipServletOutputStream class extends ServletOutputStream and writes the compressed data to the GZIPOutputStream.
    • The write method writes data to the GZIPOutputStream.


    How It All Works Together

    When a request is received, the GzipResponseCompressionFilter checks if the “Accept-Encoding” header contains “gzip” and ensures the request URI is not in the EXCLUDE_GZIP_URLS list. If both conditions are met, the filter wraps the HttpServletResponse in a GzipHttpServletResponseWrapper, which in turn wraps the output stream in a GZIPOutputStream. This setup ensures that all data written to the response is compressed using Gzip before being sent to the client.

    The CustomHttpServletResponseWrapper class manages the Gzip compression by wrapping the response’s output stream. It overrides the getOutputStream and getWriter methods to return Gzip-compressed streams, ensuring that all data sent to the client is compressed. The GzipServletOutputStream class facilitates writing the compressed data to the GZIPOutputStream.

    Output:

    By implementing this custom filter, we can selectively apply Gzip compression based on specific conditions, such as the presence of the “Accept-Encoding” header or certain URL patterns. This provides a more flexible and efficient way to optimize our application’s performance.

    5. Source Code

    The complete source code of the above example to perform conditional gzip compression can be found here.



    6. FAQs

    Why enable Gzip compression conditionally?

    How to exclude certain URLs from Gzip compression in Spring Boot?

    What does the Accept-Encoding identity header do?

    How to verify if Gzip compression is working correctly?

    What is the difference between identity and gzip values in the Accept-Encoding header?

    7. Conclusion

    By following these methods, you can enable Gzip compression for specific conditions in your Spring Boot application. Whether you choose to configure excluded user agents, use the “Accept-Encoding” header, or implement a custom filter, each approach provides you with a way to optimize your application’s performance while maintaining control over Gzip compression. Utilize these techniques to ensure your application delivers compressed responses efficiently, improving load times and user experience.



    8. Learn More

    #

    Interested in learning more?

    Checkout our blog on How to Enable Gzip Compression in Spring Boot



    Add a Comment

    Your email address will not be published.