Spring Microservices: Data Compression Techniques for Faster Responses

Alexander Obregon
9 min readOct 24, 2023

--

Image Source

Introduction

With the rise of cloud-native architectures, microservices have become an essential building block of scalable and maintainable systems. As the name suggests, microservices are small, independent services that together form a complete system. When building systems using microservices, especially those with a high volume of data exchange, it becomes crucial to ensure that the data transfer between services is fast and efficient.

One way to optimize this data transfer is through data compression techniques. In the context of Spring microservices, there are several ways to implement data compression to achieve faster responses. In this post, we’ll explore some of these techniques and understand how they can be integrated into Spring microservices.

Introduction to Data Compression

Data compression, at its core, is the art and science of reducing the amount of data required to represent information. This technology is not new, and over the years, it has become an integral part of many sectors, including data storage, multimedia, telecommunications, and more.

Understanding the Basics

At a high level, data compression can be divided into two main categories:

  • Lossless Compression: This is a type of compression in which the original data can be perfectly reconstructed from the compressed data. In other words, no information is lost during the compression process. This method is suitable for applications like text compression, where preserving every bit of information is crucial.
  • Lossy Compression: As the name implies, some data is lost during the compression process in this method. This might sound like a disadvantage, but in many scenarios, the lost data is often insignificant or undetectable to the human senses. A classic example is image and audio compression, where tiny details that humans usually don’t perceive can be removed to achieve higher compression rates.

The Relevance in Digital Age

With the ever-increasing volumes of data generated every day in the digital era, the importance of data compression has grown manifold. Here are a few reasons why:

  • Storage Savings: One of the most immediate benefits of data compression is the reduction in storage requirements. For organizations that handle vast amounts of data, the savings on storage costs can be substantial.
  • Faster Data Transfer: Compressed data means less data to move, leading to faster uploads, downloads, and data synchronization. In an era where time is of the essence, this speed boost can be critical for businesses and end-users alike.
  • Bandwidth Efficiency: For online services, especially streaming platforms, compressing data can lead to significant bandwidth savings, resulting in reduced costs and smoother experiences for users.

Methods and Algorithms

Over the years, several algorithms and methods have been developed to facilitate data compression. Some of the widely recognized ones include:

  • Huffman Coding: A popular lossless data compression algorithm. It uses variable-length codes for encoding source symbols, where the frequent symbols are given shorter codes, and less frequent ones are given longer codes.
  • Run-Length Encoding: This technique is useful for data with sequences of repeated values. It represents such sequences using a single data value and a count.
  • JPEG: A well-known lossy compression algorithm primarily used for digital images. It achieves compression by removing certain details that are less perceptible to the human eye.

Challenges and Considerations

While data compression offers numerous benefits, it’s essential to be aware of the challenges. Notably:

  • Processing Overhead: Compression and decompression require computational resources. Depending on the algorithms used and the data’s size, this can introduce a delay.
  • Data Integrity: Especially in lossy compression, there’s a trade-off between the compression rate and data quality. Over-compression can lead to a significant loss in data quality.

HTTP Response Compression with Spring

Spring Boot, a widely-used framework for building microservices, provides inherent support for HTTP response compression. This functionality is aimed at reducing the size of the HTTP response body, which, in turn, can enhance the performance of your service by reducing the amount of data transmitted over the network.

Why Use HTTP Response Compression?

Before diving into the details, let’s understand why HTTP response compression is essential:

  • Faster Data Transmission: Compressed data requires less bandwidth, which means reduced data transfer time. For services catering to clients across varying network speeds, this can ensure a more consistent user experience.
  • Reduced Server Load: Transmitting smaller data packets can reduce the load on server resources, especially the network interface.
  • Enhanced User Experience: For client-facing services, especially those serving web content, compressed responses can lead to quicker page load times.

Configuring Compression in Spring Boot

Enabling HTTP response compression in a Spring Boot application is straightforward. Here’s how you can do it:

Using application.properties:

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

Using application.yml:

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

The above configuration achieves the following:

  • enabled: This flag turns on the HTTP response compression.
  • mime-types: Specifies for which MIME types the response compression should be applied. The provided list mainly includes text and JSON types, which typically benefit most from compression.
  • min-response-size: Sets the minimum size of the response before it gets compressed. Responses smaller than this size won’t be compressed.

Under the Hood: GZIP and Deflate

Spring Boot uses standard compression algorithms, primarily GZIP and Deflate, for HTTP response compression. These algorithms are widely supported by modern browsers and HTTP clients, ensuring compatibility.

When compression is enabled, Spring Boot checks the Accept-Encoding header in the incoming request to determine which compression algorithm the client supports. Based on this, it chooses the best algorithm to compress the response.

Considerations and Best Practices

While HTTP response compression in Spring Boot is easy to set up, it’s essential to consider the following:

  • CPU Overhead: Compression requires processing power. For services with high traffic, you might observe increased CPU usage. It’s essential to monitor and scale your resources accordingly.
  • Selective Compression: Not all content benefits equally from compression. For instance, binary formats like images or videos might not see significant size reduction and could even increase in size in some cases. It’s advisable to compress primarily text-based content like HTML, CSS, JS, and JSON.
  • Cache Compressed Responses: If you have specific responses that don’t change frequently, consider caching the compressed version. This can reduce the overhead of compressing the same data repeatedly.

Data Compression with Spring Cloud Gateway

Spring Cloud Gateway serves as an API gateway in the world of microservices, offering capabilities like routing, rate limiting, and circuit breaking. Given its critical position between client applications and backend microservices, it plays a crucial role in optimizing data transmission. One of these optimizations is data compression.

Why Leverage Spring Cloud Gateway for Compression?

Utilizing Spring Cloud Gateway for compression offers a few distinct advantages:

  • Centralized Compression: Instead of handling compression at every microservice, the gateway provides a centralized point to manage and apply compression, ensuring consistency and reducing redundancy.
  • Offload Compression from Microservices: By managing compression at the gateway level, individual microservices can remain focused on their core business logic, offloading the overhead of compression to the gateway.
  • Adaptive Compression: Based on routing rules and filters, the gateway can apply different compression strategies or levels for various services or endpoints, offering adaptive compression tailored to each use case.

Configuring Response Compression in Spring Cloud Gateway

Spring Cloud Gateway builds upon the foundational features of Spring WebFlux and Netty, allowing it to support response compression. To enable this:

  • Ensure the Netty dependencies are on the classpath. If you’re using Maven, add:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
  • Configure compression in application.yml or application.properties. Here's a sample application.yml configuration:
spring:
cloud:
gateway:
routes:
- id: my_service_route
uri: http://my-service-url
predicates:
- Path=/my-service/**
filters:
- name: ModifyResponseBodyGatewayFilterFactory

While the ModifyResponseBodyGatewayFilterFactory filter can be used for various response modifications, including compression, it's essential to ensure that the backend microservices are set up to deliver the content in a compressible format or that the gateway's modifications are compression-friendly.

Compression Algorithms Supported

Spring Cloud Gateway, by default, leverages the underlying compression support provided by Netty, which includes algorithms like:

  • gzip: This is a popular algorithm that provides a good balance between compression ratio and processing speed.
  • deflate: Another widely-accepted algorithm, it’s faster than gzip but might offer slightly less compression.

The gateway will inspect the Accept-Encoding header in the incoming request to determine which compression algorithm the client supports, ensuring compatibility and efficient data transmission.

Considerations When Using Spring Cloud Gateway for Compression

  • Backend Service Responses: Ensure that the backend services’ responses are not already compressed unless the gateway is set up to handle double compression or decompress before recompressing.
  • Performance Overhead: Just as with any other layer introducing compression, the gateway will experience a computational overhead. Proper resource provisioning and monitoring are essential.
  • Testing and Validation: Always test the compressed responses, especially when using filters like ModifyResponseBodyGatewayFilterFactory, to ensure the data's integrity and that the compression is correctly applied.

Compressing Data at the Service Level

While compressing HTTP responses is beneficial, there are situations where compression within the service logic is necessary. This could be due to the need to store large datasets in a compressed format, send compressed messages over messaging systems, or handle data from sources that expect compressed input/output.

Why Compress Data at the Service Level?

Here are some motivations:

  • Optimized Storage: When storing vast amounts of data, compression can lead to significant storage savings, especially when dealing with redundant or repetitive data structures.
  • Efficient Data Exchange: For microservices that communicate via message brokers (like Kafka or RabbitMQ), sending compressed messages can enhance throughput and reduce network load.
  • Interoperability: Some external systems or services might send or expect compressed data, necessitating on-the-fly compression or decompression.

Java’s Built-in Compression Utilities

Java offers a comprehensive set of utilities for compression under the java.util.zip package. Two primary classes in this package, Deflater and Inflater, facilitate data compression and decompression, respectively.

Here’s a basic example showcasing their use:

import java.util.zip.Deflater;
import java.util.zip.Inflater;

public class CompressionUtility {

public static byte[] compressData(byte[] data) throws Exception {
Deflater deflater = new Deflater();
deflater.setInput(data);
deflater.finish();

byte[] compressedData = new byte[data.length];
int compressedDataLength = deflater.deflate(compressedData);
deflater.end();

byte[] result = new byte[compressedDataLength];
System.arraycopy(compressedData, 0, result, 0, compressedDataLength);
return result;
}

public static byte[] decompressData(byte[] compressedData) throws Exception {
Inflater inflater = new Inflater();
inflater.setInput(compressedData);

byte[] decompressedData = new byte[compressedData.length * 2];
int decompressedDataLength = inflater.inflate(decompressedData);
inflater.end();

byte[] result = new byte[decompressedDataLength];
System.arraycopy(decompressedData, 0, result, 0, decompressedDataLength);
return result;
}
}

Integrating with Spring

When integrating these utilities into a Spring service, you might create a service bean for compression tasks:

@Service
public class CompressionService {

public byte[] compress(byte[] data) throws Exception {
return CompressionUtility.compressData(data);
}

public byte[] decompress(byte[] compressedData) throws Exception {
return CompressionUtility.decompressData(compressedData);
}
}

Then, you can autowire and use this service wherever needed in your Spring components.

Considerations

  • Compression Ratio: The compression achieved depends on the nature of the data. Text data, for instance, generally compresses well, whereas already compressed formats (like JPEG images) might not see much reduction.
  • Overhead: Compressing and decompressing data introduces some overhead. It’s vital to ensure that the benefits of compression (in terms of storage or bandwidth savings) outweigh the computational cost.
  • Data Loss: Ensure you use lossless compression methods when data integrity is paramount. For scenarios where some data loss is acceptable (like multimedia streaming), lossy compression might be more suitable.

Handling Gzip-Compressed Requests in Spring Boot

Enable Spring Boot to Accept Gzip Requests

By default, Spring Boot does not handle request decompression. However, you can change this by adding a custom filter.

Implementing the Decompression Filter

Let’s create a custom filter that intercepts incoming requests. This filter will inspect the Content-Encoding header, and if the value matches "gzip", it will decompress the request.

GzipRequestFilter.java

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class GzipRequestFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

HttpServletRequest httpRequest = (HttpServletRequest) request;
String contentEncoding = httpRequest.getHeader("Content-Encoding");

if (contentEncoding != null && contentEncoding.contains("gzip")) {
request = new GzipHttpServletRequestWrapper(httpRequest);
}
chain.doFilter(request, response);
}

// Other filter methods can be overridden as needed
}

Within this filter, the main logic resides in the GzipHttpServletRequestWrapper which is a custom wrapper around the HttpServletRequest:

GzipHttpServletRequestWrapper.java

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.zip.GZIPInputStream;

public class GzipHttpServletRequestWrapper extends HttpServletRequestWrapper {

public GzipHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}

@Override
public ServletInputStream getInputStream() throws IOException {
return new GzipServletInputStream(super.getInputStream());
}
}

class GzipServletInputStream extends ServletInputStream {
private GZIPInputStream gzipInputStream;

public GzipServletInputStream(ServletInputStream servletInputStream) throws IOException {
this.gzipInputStream = new GZIPInputStream(servletInputStream);
}

@Override
public int read() throws IOException {
return gzipInputStream.read();
}

// Other required methods from ServletInputStream can be overridden as needed
}

Register the Filter

In your Spring Boot configuration (could be an @Configuration class or within a @SpringBootApplication class), you should register the filter.

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;

// ... Other annotations and class definition ...

@Bean
public FilterRegistrationBean<GzipRequestFilter> loggingFilter(){
FilterRegistrationBean<GzipRequestFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new GzipRequestFilter());
registrationBean.addUrlPatterns("/your_endpoint"); // or "/*" for all endpoints
return registrationBean;
}

Testing the Implementation

You can use curl to test the functionality:

curl -X POST -H "Content-Encoding: gzip" --data-binary @your_compressed_file.gz http://localhost:8080/your_endpoint

With these steps and code snippets, your Spring Boot application should now be capable of handling Gzip-compressed requests seamlessly.

Conclusion

Data compression is an essential technique for optimizing communication between microservices, especially in scenarios where there’s a high volume of data exchange. Whether you’re using out-of-the-box support from Spring Boot for HTTP response compression, utilizing Spring Cloud Gateway’s features, or compressing data manually at the service level, ensuring efficient data transfer will lead to faster responses and a better user experience.

  1. Spring Boot Official Documentation
  2. Java’s Deflater and Inflater Documentation
  3. HTTP Compression
Spring Boot icon by Icons8

--

--

Alexander Obregon

Software Engineer, fervent coder & writer. Devoted to learning & assisting others. Connect on LinkedIn: https://www.linkedin.com/in/alexander-obregon-97849b229/