Skip Navigation
Show nav
Heroku Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up
View categories

Categories

  • Heroku Architecture
    • Dynos (app containers)
    • Stacks (operating system images)
    • Networking & DNS
    • Platform Policies
    • Platform Principles
  • Command Line
  • Deployment
    • Deploying with Git
    • Deploying with Docker
    • Deployment Integrations
  • Continuous Delivery
    • Continuous Integration
  • Language Support
    • Node.js
    • Ruby
      • Working with Bundler
      • Rails Support
    • Python
      • Background Jobs in Python
      • Working with Django
    • Java
      • Working with Maven
      • Java Database Operations
      • Working with the Play Framework
      • Working with Spring Boot
      • Java Advanced Topics
    • PHP
    • Go
      • Go Dependency Management
    • Scala
    • Clojure
  • Databases & Data Management
    • Heroku Postgres
      • Postgres Basics
      • Postgres Getting Started
      • Postgres Performance
      • Postgres Data Transfer & Preservation
      • Postgres Availability
      • Postgres Special Topics
    • Heroku Data For Redis
    • Apache Kafka on Heroku
    • Other Data Stores
  • Monitoring & Metrics
    • Logging
  • App Performance
  • Add-ons
    • All Add-ons
  • Collaboration
  • Security
    • App Security
    • Identities & Authentication
    • Compliance
  • Heroku Enterprise
    • Private Spaces
      • Infrastructure Networking
    • Enterprise Accounts
    • Enterprise Teams
    • Heroku Connect (Salesforce sync)
      • Heroku Connect Administration
      • Heroku Connect Reference
      • Heroku Connect Troubleshooting
    • Single Sign-on (SSO)
  • Patterns & Best Practices
  • Extending Heroku
    • Platform API
    • App Webhooks
    • Heroku Labs
    • Building Add-ons
      • Add-on Development Tasks
      • Add-on APIs
      • Add-on Guidelines & Requirements
    • Building CLI Plugins
    • Developing Buildpacks
    • Dev Center
  • Accounts & Billing
  • Troubleshooting & Support
  • Integrating with Salesforce
  • Language Support
  • Java
  • Java Advanced Topics
  • HTTP Caching in Java with JAX-RS

This article was contributed by Alesandro Lang

Alesandro Lang is developer from Nuremberg Germany. He is working in a bank datacenter and has a passion for new Java and web technologies.

HTTP Caching in Java with JAX-RS

English — 日本語に切り替える

Last updated February 20, 2019

Table of Contents

  • Time-based cache headers
  • Conditional cache headers

HTTP caching is an easy and oft-ignored option to speed up web applications. It’s standardized and well implemented in all modern browsers and results in a lower latency app and improved responsiveness.

Please see the increasing application performance with HTTP cache headers article for a more thorough discussion of HTTP cache headers.

Java offers a wide variety of frameworks to build a REST-API. This article focuses on implementing HTTP caching with the JAX-RS framework.

Time-based cache headers

In HTTP 1.1 the Cache-Control header specifies the resource caching behavior as well as the max age the resource can be cached. As an example, this response would be cached for one day:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=86400
Last-Modified: Thu, 07 Feb 2013 11:56 EST

Here is a list of all the available Cache-Control tokens and their meaning:

  • private only clients (mostly the browser) and no one else in the chain (like a proxy) should cache this
  • public any entity in the chain can cache this
  • no-cache should not be cached anyway
  • no-store can be cached but should not be stored on disk (most browsers will hold the resources in memory until they will be quit)
  • no-transform the resource should not be modified (for example shrink image by proxy)
  • max-age how long the resource is valid (measured in seconds)
  • s-maxage same like max-age but this value is just for non clients

Setting the Cache-Control header

In a JAX-RS method a Response object can be returned. The content will be auto transformed according to the specified MIME-Type (this is called JAX-RS Content Negotiation). The Cache-Control header can also be set on the Response object by using the CacheControl class. Methods are provided for all of the available Cache-Control header tokens.

@Path("/book/{id}")
@GET
public Response getBook(@PathParam("id") long id){

    Book myBook = getBookFromDB(id);

    CacheControl cc = new CacheControl();
    cc.setMaxAge(86400);
    cc.setPrivate(true);

    ResponseBuilder builder = Response.ok(myBook);
    builder.cacheControl(cc);
    return builder.build();
}

Screenshot of response headers attributes

Conditional cache headers

Conditional requests are those where the browser can ask the server if it has an updated copy of the resource. The browser will send one or both of the ETag and If-Modified-Since headers about the cached resource it holds. The server can then determine whether updated content should be returned or the browser’s copy is the most recent.

Making a conditional GET

In JAX-RS the Request object has a method to check wether the data based on the date or the etag from the client was modified. The ETag value should be a unique hash of the resource and requires knowledge of internal business logic to construct. One possibility is to use the HashCode of a object as the etag.

@Path("/book/{id}")
@GET
public Response getBook(@PathParam("id") long id, @Context Request request){

    Book myBook = getBookFromDB(id);
    CacheControl cc = new CacheControl();
    cc.setMaxAge(86400);

    EntityTag etag = new EntityTag(Integer.toString(myBook.hashCode()));
    ResponseBuilder builder = request.evaluatePreconditions(etag);

    // cached resource did change -> serve updated content
    if(builder == null){
        builder = Response.ok(myBook);
        builder.tag(etag);
    }

    builder.cacheControl(cc);
    return builder.build();
}

A ResponseBuilder is automatically constructed by calling evaluatePreconditions on the request. If the builder is null, the resource is out of date and needs to be sent back in the response. Otherwise, the preconditions indicate that the client has the latest version of the resource and the 304 Not Modified status code will be automatically assigned.

Response with 200 OK

Response with 304 Not Modified

Making a conditional PUT

Resource updates can also be conditional. The client would receive 412 Precondition Failed if the ETag send by the client does not match the one on the server. Otherwise the update could be made and a 200 OK with the updated entity or a 204 No Content would be send back.

@Path("/book/{id}")
@PUT
@Consumes("application/json")
public Response getBook(@PathParam("id") long id, @Context Request request, Book updatedBook){

    Book myBook = getBookFromDB(id);
    EntityTag etag = new EntityTag(Integer.toString(myBook.hashCode()));

    ResponseBuilder builder = request.evaluatePreconditions(etag);

    // client is not up to date (send back 412)
    if(builder != null){
        return builder.build();
    }

    updateBookInDB(updatedBook);

    builder = Response.noContent();
    return builder.build();
}

Response with 204 No Content

Keep reading

  • Java Advanced Topics

Feedback

Log in to submit feedback.

Warming Up a Java Process JVM Runtime Metrics

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Podcasts
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing

Subscribe to our monthly newsletter

Your email address:

  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Heroku Podcasts
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Facebook
  • Instagram
  • Github
  • LinkedIn
  • YouTube
Heroku is acompany

 © Salesforce.com

  • heroku.com
  • Terms of Service
  • Privacy
  • Cookies
  • Cookie Preferences