Preparing a Java Web App for Production on Heroku
Last updated June 14, 2023
Table of Contents
It is important to ensure that an app is secure, scalable, and resilient to failure before sending it to production. This guide provides an overview of important steps required to make a Java web application production-ready on Heroku. If you are using Spring Boot, you might prefer the article on Preparing a Spring Boot App for Production.
Force the use of HTTPS
Unless you have very specific needs, your app should be using HTTPS for all requests. Heroku provides an HTTPS URL (of the form https://<app-name>-<random-identifier>.herokuapp.com
) for every app, as well as free tools for adding your own domains and certificates.
You can enforce the use of HTTPS when your app is running on Heroku by creating a Servlet filter in your app with the following code:
public class HttpsEnforcer implements Filter {
public static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (request.getHeader(X_FORWARDED_PROTO) != null) {
if (request.getHeader(X_FORWARDED_PROTO).indexOf("https") != 0) {
String pathInfo = (request.getPathInfo() != null) ? request.getPathInfo() : "";
response.sendRedirect("https://" + request.getServerName() + pathInfo);
return;
}
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() { }
}
This configuration instructs the servlet container to redirect all plain HTTP requests back to the same URL using HTTPS if the X-Forwarded-Proto
header is present. Heroku sets the X-Forwarded-Proto
header for you, which means the request will be redirected back through the Heroku router where SSL is terminated. In your localhost
environment, you can continue to use plain HTTP.
Rate-limit API calls
Rate limiting is the process of controlling traffic to a server based on client IPs, blocked IPs, geolocation, and other factors. One of the most popular rate-limiting libraries for Java is Bucket4j, which can be used by creating another Servlet filter in your application. First, add the following dependency to your pom.xml
:
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
</dependency>
The create a Servlet filter with the following code:
public class ThrottlingFilter implements javax.servlet.Filter {
private Bucket createNewBucket() {
long overdraft = 50;
Refill refill = Refill.greedy(10, Duration.ofSeconds(1));
Bandwidth limit = Bandwidth.classic(overdraft, refill);
return Bucket4j.builder().addLimit(limit).build();
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
HttpSession session = httpRequest.getSession(true);
String appKey = SecurityUtils.getThirdPartyAppKey();
Bucket bucket = (Bucket) session.getAttribute("throttler-" + appKey);
if (bucket == null) {
Bucket bucket = createNewBucket();
session.setAttribute("throttler-" + appKey, bucket);
}
if (bucket.tryConsume(1)) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
httpResponse.setContentType("text/plain");
httpResponse.setStatus(429);
httpResponse.getWriter().append("Too many requests");
}
}
}
Other examples can be found in the Bucket4j documentation.
Use a distributed session store
Storing sessions in-memory inhibits the disposability and horizontal scalability of dynos. That’s why it’s important to instead use a distributed session store like Redis.
There are many ways to implement session storage with Java and Redis that depend on your server and frameworks. Heroku recommends using Redisson, which works with most popular options, as described in this article on Java Session Handling.
Enable JVM Runtime Metrics
The JVM Runtime Metrics feature allows you to view heap memory, non-heap memory, and garbage collection activity for any app that runs inside of the Java Virtual Machine (JVM). The feature is safe to use in production and can help you identify performance-related problems.
Configure alerting
When your application experiences a problem, it should alert a human. At a minimum, the app should:
- Alert a human if it is down
- Alert a human if error rates exceed specific thresholds
- Alert a human if latency is high
Heroku’s Threshold Alerting feature is available to apps running on Professional dynos. You can also choose from the many alerting and monitoring add-ons in the Heroku Add-on Marketplace.
Configure error and maintenance pages
Heroku lets you configure the static HTML pages that are shown to users when your application experiences an error (such as crashing) or goes down for maintenance. Please see Customizing Error Pages for more information.
Attach a logging add-on
By default, Heroku stores 1500 lines of logs from your application. However, it makes the full log stream available as a service that several add-on providers consume to provide features such as log persistence, search, and alerts via email or SMS.
You can provision one of these logging add-ons, Papertrail, by running the following command on your app:
$ heroku addons:create papertrail
To see Papertrail in action, visit your application’s Heroku URL a few times. Each visit generates more log messages, which should be routed to the add-on.
Attach an error-tracking add-on
While your application logs capture the regular activity of your server processes, an error-tracking add-on can capture more detail for exceptional cases. This can be useful in identifying common problems your users encounter, or to diagnose poor performance. The Add-on Marketplace has a number of options including the Rollbar service, which you can add to your app by running:
$ heroku addons:create rollbar
After following the guide for setting up Rollbar integration, you’ll begin to see a record of all errors with their associated stack traces and other details coming from your app.
Attach a vulnerability detection add-on
The final add-on every production app should use is one that detects security vulnerabilities in your source code. One such service is Snyk, which you can attach to your app by running:
$ heroku addons:create snyk
Snyk scans your source code and compares the dependencies in either your pom.xml
or build.gradle
file against its database of known security vulnerabilities. If it finds any, it sends a notification that describes how to resolve them.