How to compress response HTML in Java web application

16 January 2014
By Gonçalo Marques
In this tutorial we will how to compress (or minify) the response HTML in a Java web application.

Introduction

Nowadays it's important to keep the size of a web application response to the minimum. By reducing the application response size we will increase the overall response data transfer speed while decreasing the required bandwidth.

In this tutorial we will see how to minify the response of a Java web application. We will be using the htmlcompressor project:

http://code.google.com/p/htmlcompressor

We will use the following Maven dependencies (htmlcompressor is available at the Maven Central Repository):

Required Maven dependencies

<dependencies>
  <dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>6.0</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>com.googlecode.htmlcompressor</groupId>
    <artifactId>htmlcompressor</artifactId>
    <version>1.5.2</version>
  </dependency>
</dependencies>

The HTML compressor

The HTML compressor we will be using has a lot of configuration options. You may refer to the project page mentioned in the introduction. A simple initialization of the HTML compressor may look like the following:

Compressor initialization

HtmlCompressor compressor = new HtmlCompressor();

As the documentation states the compressor initialization may be expensive so one should initialize a single compressor instance and use it across multiple requests.

Another important detail is that the compressor instance is thread safe as long as we don't use statistics generation. This means that if you don't use the statistics generation feature you may use a single compressor instance to process multiple requests simultaneously.

The compression filter

We will use a standard Java web application filter in order to compress our web application responses:

Compression filter

package com.byteslounge.web.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;

import com.googlecode.htmlcompressor.compressor.HtmlCompressor;

@WebFilter(
  filterName = "CompressResponseFilter",
  urlPatterns = { "/pages/*" }
)
public class CompressResponseFilter implements Filter {

  private HtmlCompressor compressor;

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

    CharResponseWrapper responseWrapper = new CharResponseWrapper(
        (HttpServletResponse) resp);

    chain.doFilter(req, responseWrapper);

    String servletResponse = new String(responseWrapper.toString());
    resp.getWriter().write(compressor.compress(servletResponse));
  }

  @Override
  public void init(FilterConfig config) throws ServletException {
    compressor = new HtmlCompressor();
    compressor.setCompressCss(true);
    compressor.setCompressJavaScript(true);
  }

  @Override
  public void destroy() {
  }

}

We have just defined a standard web application filter that initializes the compressor during filter initialization. One may see that we configured the compressor to enable inline CSS and JavaScript compression.

This tutorial will not cover details that are specific to servlets and filters, but as you probably already know a servlet will write the response directly to the client by default. In short this means that when the execution returns to our filter after chain.doFilter() the response would have already been streamed to the client.

In this case we have to override this behaviour: We need the response before it's streamed to the client in order to compress it.

This is achieved using an HttpServletResponseWrapper. The CharResponseWrapper we are using extends this class and its definition follows next:

CharResponseWrapper

package com.byteslounge.web.filter;

import java.io.CharArrayWriter;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class CharResponseWrapper extends HttpServletResponseWrapper {

  private final CharArrayWriter output;

  @Override
  public String toString() {
    return output.toString();
  }

  public CharResponseWrapper(HttpServletResponse response) {
    super(response);
    output = new CharArrayWriter();
  }

  @Override
  public PrintWriter getWriter() {
    return new PrintWriter(output);
  }

}

Once again, this tutorial will not focus in servlet or filter details, but we may say that by using the wrapper we will make the container write the response into the underlying wrapper data holding structure (CharArrayWriter) instead of writing it directly to the client.

Since we are using the wrapper we may obtain the full response in our filter before it's sent to the client, and then apply the compression.

Note: Keep in mind that since we are using a servlet response wrapper the response will be fully kept in memory instead of being progressively streamed to the client by the container. After the compression is done and the response if finally flushed to the client the used memory will be ready to be freed by the garbage collector as usual.

Downloadable sample

At the end of this page you may find a downloadable sample that illustrates this scenario. The sample consists of a Java web application containing a single page that will be compressed when accessed ([app-context-root]/pages/page.jsp).

If you inspect the testing page HTML source in your web browser after accessing the URL you will observe that the HTML is compressed within a single line.

Download source code from this article

Related Articles

Comments

About the author
Gonçalo Marques is a Software Engineer with several years of experience in software development and architecture definition. During this period his main focus was delivering software solutions in banking, telecommunications and governmental areas. He created the Bytes Lounge website with one ultimate goal: share his knowledge with the software development community. His main area of expertise is Java and open source.

GitHub profile: https://github.com/gonmarques

He is also the author of the WiFi File Browser Android application: