Performance Optimization Best Practice #3: Implementing Caching in Jakarta EE

This article explains the different cache layers—client, application, database, and distributed—provides best‑practice guidelines, and shows how to configure client‑side headers, application‑side caches with Hazelcast, JPA second‑level caching, and JMS‑based cache coordination in GlassFish.

JakartaEE China Community
JakartaEE China Community
JakartaEE China Community
Performance Optimization Best Practice #3: Implementing Caching in Jakarta EE

Cache Levels

Caching is a powerful technique for improving performance and scalability. The article outlines four cache levels: client‑side (browser static‑resource caching to reduce server requests), application‑side (in‑memory caches such as Redis, Memcached, Hazelcast, or Infinispan that lower database load), database cache (result‑set caching to cut query time), and distributed cache (shared across servers to enhance scalability).

Client‑side Caching in GlassFish

By default GlassFish does not set any cache headers, so browsers may cache static resources for a long time. To control caching, add a servlet filter that sets Cache‑Control and Expires headers, for example caching files with specific extensions for one week.

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.Duration;

@WebFilter(urlPatterns = {"*.css", "*.js", "*.png", "*.jpg", "*.gif", "*.woff2", "*.svg", "*.html"})
public class CacheControlFilter implements Filter {
    private static final long ONE_WEEK_IN_SECONDS = Duration.ofDays(7).toSeconds();
    private static final long ONE_WEEK_IN_MILLIS = Duration.ofDays(7).toMillis();

    @Override
    public void doFilter(ServletRequest request, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) resp;
        response.setHeader("Cache-Control", "public, max-age=" + ONE_WEEK_IN_SECONDS);
        long expiresInMillis = System.currentTimeMillis() + ONE_WEEK_IN_MILLIS;
        response.setDateHeader("Expires", expiresInMillis);
        chain.doFilter(request, response);
    }
}

Application‑side Caching

In‑memory caches such as Redis or Memcached store frequently accessed data close to the application, reducing repeated processing or database queries. Hazelcast and Infinispan also implement the standard JCache API and can run inside the same JVM.

Integrate Hazelcast into GlassFish via a JCA adapter (Java EE 8) or use Hazelcast directly with CDI.

Other cache solutions can be used similarly.

Database Caching

When accessing the database through Jakarta Persistence (JPA), GlassFish uses EclipseLink to provide a first‑level cache (per EntityManager) and a second‑level cache (shared across EntityManagers). These caches are enabled automatically, but developers must manage invalidation to avoid stale data.

Warning: The second‑level cache lives for the entire application lifecycle. If the database is modified outside the application, the cache can become inconsistent.

External modifications (manual edits, scripts, other applications).

Direct JDBC updates performed by the same application outside the persistence context.

Updates performed by another instance in a clustered deployment.

For the first case, expose a hook (e.g., a REST endpoint) that calls the JPA cache eviction API:

em.getEntityManagerFactory().getCache().evictAll();

For the second and third cases, execute cache‑clearing code after the database operation or at transaction end.

Distributed Cache Coordination

To keep caches consistent across cluster nodes, EclipseLink can coordinate via JMS. GlassFish provides a built‑in JMS broker, making the setup straightforward. The steps are:

Create a non‑JTA, non‑persistent JMS topic for coordination.

Configure EclipseLink to use the JMS protocol and the created topic.

Reference the JMS resources via JNDI in persistence.xml.

<property name="eclipselink.cache.coordination.protocol" value="jms"/>
<property name="eclipselink.cache.coordination.jms.topic" value="jms/CoordinationTopic"/>
<property name="eclipselink.cache.coordination.jms.factory" value="jms/CoordinationConnectionFactory"/>

Next Steps

Identify the parts of the application where caching yields the greatest benefit, experiment with different strategies, and measure performance improvements while managing cache invalidation. The next article will cover Jakarta EE server runtime tuning, including JVM options and thread‑pool configuration.

Performance optimizationCachingjakarta-eejpaHazelcastGlassFish
JakartaEE China Community
Written by

JakartaEE China Community

JakartaEE China Community, official website: jakarta.ee/zh/community/china; gitee.com/jakarta-ee-china; space.bilibili.com/518946941; reply "Join group" to get QR code

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.