Skip to main content

How to efficiently cache static resources in a web application

This article is about an efficient technique of caching static resources on the client side (user's browser). There are quite many ways to implement caching including HTTP headers. A common disadvantage of them is that as soon as some resource is cached by the browser, it won't be updated until it's expired. Thus, the server loses control over the browser caching mechanism for some period of time. As a result we have to adjust the expiration time period or updating frequency somehow.

The better alternative is the case when it's only the server that is in charge of the browser caching mechanism. Actually the idea has been borrowed here. If we append a GET parameter with some changing value (like a timestamp or a version) for a static resource URL, it'll force the browser to get the fresh version of the resource from the server. If we keep the parameter value steady, the resource will be taken from the browser cache.

Finally this article is about implementation. I have a Java web application being built by the Maven tool. We're going to exploit the placeholder substitution mechanism provided by Maven Resource plugin filtering feature. First you need to configure the resources filtering properly in your project pom file. Second the links to static resources need to be appended with the GET parameter.

Modifying project pom file
<!-- Defines the placeholder property name and value -->

<!-- The first resource set defines the files to be filtered and
     the other resource set defines the files to copy unaltered -->
As you can see, I've used the built-in Maven timestamp variable with the specific format. Also I configured *.jx resources to be looked for the placeholder name. It's because I use JX templates in my Cocoon based web application. Probably you'll have to modify this according to your needs.

Updating links to static resources
<link type="text/css" rel="stylesheet" 
<script type="text/javascript" 
Here you can see updated URLs for static application resources. Now upon each Maven build the ${build.timestamp} placeholder will be evaluated to the current timestamp in the predefined format. Deploying this application build will cause specified resources to be updated on the client browsers.

Possible issue with Maven placeholder substitution (UPDATE from 14.12.2011)
Our recent findings made us revert above changes and use an alternative solution. The root cause is the Maven placeholder substitution behaved unexpectedly. It substituted ${id} string value for the artifact id (like org.lagivan.myapp:myblock:1.0-SNAPSHOT). However, ${id} is a parameter in JX template but not supposed to be a Maven placeholder. Unfortunately, I couldn't find in the web the complete tutorial on all maven built-in properties (only this one). So be careful with substitution and test your applications properly.

Meanwhile I'll describe our alternative solution as well here. But it's limited to Cocoon 2.2 web applications that are based on pipelines processing. The solution is based on this article and uses InputModule Spring bean to provide values directly in the sitemap. I'll provide the code snippets with my comments below.
package org.lagivan.myapp.inputmodule;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.cocoon.components.modules.input.InputModule;

import java.text.SimpleDateFormat;
import java.util.*;

 * This input module returns a timestamp to be used for static resources URLs mainly.
 * @author Ivan Lagunov
public class TimeModule implements InputModule {

    private static final String ATTR_BUILD = "build";
    private static final String TIMESTAMP_FORMAT = "yyyyMMddHHmm";
    private static String BUILD_TIMESTAMP;
    static {
        BUILD_TIMESTAMP = new SimpleDateFormat(TIMESTAMP_FORMAT).format(new Date());
    public Object getAttribute(String name, Configuration modeConf, Map objectModel) throws ConfigurationException {
        if (name.equals(ATTR_BUILD)) {
            return BUILD_TIMESTAMP;
        } else {
            return "";

    public Iterator getAttributeNames(Configuration modeConf, Map objectModel) throws ConfigurationException {
        return Arrays.asList(BUILD_TIMESTAMP).iterator();

    public Object[] getAttributeValues(String name, Configuration modeConf, Map objectModel) throws ConfigurationException {
        if (name.equals(ATTR_BUILD)) {
            return new String[] { BUILD_TIMESTAMP };
        } else {
            return new Object[0];
The main idea is that this class implements InputModule interface. Read the article referenced above for technical details. Also it's a bit different to Maven solution as this one provides timestamp of the last build deployment or the application server restart (not the actual build timestamp).

<bean name="org.apache.cocoon.components.modules.input.InputModule/time"
This is a piece of Spring Cocoon module configuration. The bean is restricted to be named specifically (meaningful name after a slash will be used in the sitemap).

<map:match pattern="home.html">
    <map:generate src="page/home.jx" type="jx">
        <map:parameter name="timestamp" value="{time:build}"/>
    <map:serialize type="xhtml"/>
Here the timestamp parameter has been added to JXTemplateGenerator. The file home.jx includes head.jx, I won't provide source for the first one for simplicity.

<link type="text/css" rel="stylesheet" 
<script type="text/javascript" 
Finally placeholders in the resource URLs need be modified as it's shown above.


  1. Hi Ivan,

    please update this article with our recent findings on using this strategy as we don't want other developers to face the same issues.


  2. Updated. Thanks for pointing to this!


Post a Comment

Popular posts from this blog

Connection to Amazon Neptune endpoint from EKS during development

This small article will describe how to connect to Amazon Neptune database endpoint from your PC during development. Amazon Neptune is a fully managed graph database service from Amazon. Due to security reasons direct connections to Neptune are not allowed, so it's impossible to attach a public IP address or load balancer to that service. Instead access is restricted to the same VPC where Neptune is set up, so applications should be deployed in the same VPC to be able to access the database. That's a great idea for Production however it makes it very difficult to develop, debug and test applications locally. The instructions below will help you to create a tunnel towards Neptune endpoint considering you use Amazon EKS - a managed Kubernetes service from Amazon. As a side note, if you don't use EKS, the same idea of creating a tunnel can be implemented using a Bastion server . In Kubernetes we'll create a dedicated proxying pod. Prerequisites. Setting up a tunnel.

DynamicReports and Spring MVC integration

This is a tutorial on how to exploit DynamicReports reporting library in an existing  Spring MVC based web application. It's a continuation to the previous post where DynamicReports has been chosen as the most appropriate solution to implement an export feature in a web application (for my specific use case). The complete code won't be provided here but only the essential code snippets together with usage remarks. Also I've widely used this tutorial that describes a similar problem for an alternative reporting library. So let's turn to the implementation description and start with a short plan of this how-to: Adding project dependencies. Implementing the Controller part of the MVC pattern. Modifying the View part of the MVC pattern. Modifying web.xml. Adding project dependencies I used to apply Maven Project Builder throughout my Java applications, thus the dependencies will be provided in the Maven format. Maven project pom.xml file: net.sourcefo

Cocoon authentication

This article will guide you through the steps showing how to use the Authentication Framework in a Cocoon 2.2 application. Maven dependencies. Spring configuration. Sitemap. Login page and controls. Maven dependencies You need the following dependency in your pom.xml : <dependency> <groupId>org.apache.cocoon</groupId> <artifactId>cocoon-auth-impl</artifactId> <version>1.0.0</version> </dependency> Spring configuration Authentication Framework has a flexible configuration based on a concepts of applications and security handlers . There can be several applications defined and running at the same that are simply independent security zones of your web application. The security details of an application are specified using a security handler. There are several implementations provided and you're free to implement your own. Here is the SimpleSecurityHandler used that takes the hardcoded credentials: <?xml versio