Compression and HTTP Tuning

Response Compression

JSON compresses very well. A 120 KB product list becomes about 15 KB with gzip, which matters on Indian mobile networks.

Example

application.yml

server:
  compression:
    enabled: true
    mime-types: application/json,text/html,text/css,application/javascript,text/csv
    min-response-size: 1KB
  http2:
    enabled: true
  tomcat:
    threads:
      max: 200
    connection-timeout: 5s

Example

curl -s -o /dev/null -w "%{size_download}\n" localhost:8080/api/products?size=100
curl -s -o /dev/null -w "%{size_download}\n" -H "Accept-Encoding: gzip" localhost:8080/api/products?size=100

Example output

122841
15276

Gzip in Plain Java

Example

See the effect with java.util.zip on a repetitive JSON payload:

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPOutputStream;

public class Main {
    public static void main(String[] args) throws IOException {
        StringBuilder json = new StringBuilder("[");
        for (int i = 1; i <= 500; i++) {
            if (i > 1) json.append(',');
            json.append("{\"id\":").append(i)
                .append(",\"name\":\"Cotton T-Shirt size M\",\"price\":499.00,\"category\":\"fashion\",\"stock\":")
                .append(i % 50).append('}');
        }
        json.append(']');
        byte[] raw = json.toString().getBytes(StandardCharsets.UTF_8);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (GZIPOutputStream gz = new GZIPOutputStream(out)) { gz.write(raw); }

        System.out.println("raw bytes:     " + raw.length);
        System.out.println("gzipped bytes: " + out.size());
        System.out.printf("saved: %.0f%%%n", 100.0 * (raw.length - out.size()) / raw.length);
    }
}

Output

raw bytes:     44293
gzipped bytes: 1683
saved: 96%

HTTP Caching Headers

@GetMapping("/api/categories")
public ResponseEntity<List<CategoryDto>> categories() {
    List<CategoryDto> list = categoryService.findAll();
    return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(Duration.ofMinutes(10)).cachePublic())
            .eTag(Integer.toHexString(list.hashCode()))
            .body(list);
}

With an ETag, the browser sends If-None-Match next time and gets 304 Not Modified with an empty body if nothing changed. Add ShallowEtagHeaderFilter to do this for every GET automatically.