Preparing a Spring Boot App for Production on Heroku
Last updated June 14, 2023
Table of Contents
- Force the use of HTTPS
- Rate-limit API calls
- Use structured logging
- Use UUIDs for all Postgres Primary Keys
- Remove secrets from source code
- Use a distributed session store
- Enable JVM Runtime Metrics
- Configure alerting
- Configure error and maintenance pages
- Attach a logging add-on
- Attach an error tracking add-on
- Attach a vulnerability detection add-on
- Further Reading
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 many common and important steps required to make a Spring Boot app production-ready on Heroku.
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 (in 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 adding the following configuration to your Spring Boot app.
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requiresChannel()
.requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
.requiresSecure();
}
}
If you already have a WebSecurityConfigurerAdapter
implementation, then add the above configuration to it.
This configuration tells Spring 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.
For more information see the official Spring Boot docs on how to Enable HTTPS When Running behind a Proxy Server.
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 with Spring Boot via the Spring Boot Starter for Bucket4j.
After adding the dependency and configuring a back-end, some minimal configuration might look like this in your application.yml
:
bucket4j:
enabled: true
filters:
- cache-name: buckets
url: /*
rate-limits:
- bandwidths:
- capacity: 5
time: 10
unit: seconds
This configuration limits an individual user to a maximum of 5 requests within a 10 second period.
Use structured logging
Structured logging makes it easier to search and perform analytic operations on logging data. You can use Logback to encode your Spring Boot logs into JSON format by adding the following dependency to your app:
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
</dependency>
Then create a logback.xml
configuration file in the src/main/resources
directory with the following contents:
<configuration>
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<logger name="jsonLogger" additivity="false" level="DEBUG">
<appender-ref ref="consoleAppender"/>
</logger>
<root level="INFO">
<appender-ref ref="consoleAppender"/>
</root>
</configuration>
The result will be log lines that look like this:
{"@timestamp":"2018-04-16T09:36:34.641-05:00","@version":"1","message":"Started Main in 4.154 seconds (JVM running for 4.832)","logger_name":"com.example.Main","thread_name":"restartedMain","level":"INFO","level_value":20000}
JSON formatted logs may be more difficult to read when using the heroku logs
command, but they are far easier to search in a real production environment. Thus, you may choose to retain a human-readable log format in non-production environments.
Use UUIDs for all Postgres Primary Keys
By default, Spring Data will use integers for primary keys in a Postgres database. However, this can leave your app vulnerable to exploits were attackers guess the next record identifier. It is preferable to use UUID primary keys, which cannot be guessed or enumerate. To configure your Spring Data models with UUID primary keys, use the @GeneratedValue
annotation in conjunction with the java.util.UUID
type like this:
@Entity
class MyModel {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
}
In this way, a random UUID is assigned when the model is created. Then add a UUID AttributeConverter
to your app like this:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.util.UUID;
@Converter(autoApply = true)
public class UUIDConverter implements AttributeConverter<UUID, UUID> {
@Override
public UUID convertToDatabaseColumn(UUID attribute) {
return attribute;
}
@Override
public UUID convertToEntityAttribute(UUID dbData) {
return dbData;
}
}
UUIDs can be freely exposed without disclosing sensitive information. They are also unpredictable, perform well, and do not suffer from integer rollover.
Remove secrets from source code
It is unsafe to keep sensitive information in source code (i.e. checked into Git). Source code is stored in clear-text and is not encrypted on the server. Furthermore, storing secrets in source code increases the risk of accidentally connecting to a production service from a staging or test service.
All sensitive data (such as passwords, API tokens, and private keys) should be stored as configuration variables using the heroku config
command. You can then access using environment variable substitution in your application.yml
like this:
admin:
password: ${ADMIN_PASSWORD}
Note that most relational database URLs require zero-configuration on Heroku when used with Spring.
Use a distributed session store
Storing sessions in-memory prohibits disposability and horizontal scalability of dynos. That’s why it’s important to use a distributed session store like Redis. Spring Boot has multiple mechanisms for storing sessions in Redis, including the Spring Session module for Redis. However, Heroku recommends using Redisson as described in our 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, it should:
- Alert a human if it is down
- Alert a human if error rates go above specific thresholds
- Alert a human if latency is high
Heroku’s Threshold Alerting feature is available to apps running on Professional dynos. But you can also choose from the many Alerting and Monitoring add-ons in the Heroku Add-on Marketplace.
Configure error and maintenance pages
Heroku provides a mechanism for configuring 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 the Dev Center guide to 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 this particular add-on in action, visit your application’s Heroku URL a few times. Each visit will generate more log messages, which should now get routed to the papertrail 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 will scan your source code, and compare the dependencies in either your pom.xml
or build.gradle
against its database of known security vulnerabilities. If it finds any it will send a notification telling you how to update it.
Further Reading
For more information and guidance on running Spring Boot apps in production, please see the official Spring documentation on Moving to Production