Getty Images

How to build a Spring Boot 12-Factor app

Here we look at how the Spring Boot framework supports the Twelve-Factor App methodology, and where tools such as GitHub, Docker and Kubernetes fill in the voids.

There is no international standards organization that specifies the criteria a Spring Boot application has to satisfy to qualify as a microservice. The closest thing developers have to a set of guidelines for cloud-native development is the set of 12 recommendations that Heroku co-founder Adam Wiggins provided to developers who deployed to the Heroku platform.

Better known as the Twelve-Factor App methodology, these 12 commandments have become the de facto standard for the creation of modern, cloud-native microservices that use Docker and Kubernetes as their deployment target.

The most popular platform for developing Java-based microservices is Spring Boot. Here's how Spring Boot supports the tenets of the Twelve-Factor App methodology.

1. Spring Boot codebases

Not every 12-Factor principle maps directly onto Spring Boot. The codebase principle is one example where the responsibility falls outside of Spring framework.

According to the Twelve-Factor App, Spring Boot microservices should each have their own, independent codebase. This can be achieved through the creation of a single Git repository, in which developers contribute code, merge branches and fix bugs. If your Spring Boot app is hosted in its own Git repository, then you've implemented the 12-Factor codebase requirement correctly.

Diagram of the Twelve Factor App methodology

2. Externalize dependency management

If you create a project with the Spring Boot initializer, you must choose between Gradle or Maven as the project's build tool. Both of these tools externalize the management dependencies.

If you keep your JAR files out of the project's lib directory and list all of your program's external dependencies in either Maven POM or Gradle build files, your Spring Boot app will implement 12-Factor dependency management correctly.

3. Spring Boot and Kubernetes configuration

According to the Twelve-Factor App methodology, a Spring Boot application should read its configuration data from the environment. For example, if a cloud-native Spring Boot app is deployed to a Docker container and managed within a Kubernetes cluster, the application should read its configuration data from a Kubernetes ConfigMap -- not from JavaBean fields or even application properties files. Spring's cascading configuration processing system fully addresses this 12-Factor requirement.

Any JavaBean decorated with the Spring @ConfigurationProperties annotation will look for configuration data in multiple places. The rule of a @ConfigurationProperties decorated bean is that the most highly externalized level of configuration is always used. Properties hard-coded in a JavaBean will be overridden by data in the ApplicationProperties file, which will be overridden by JVM arguments, which finally will be overridden by arguments provided by Docker or a Kubernetes ConfigMap.

Use the @ConfigurationProperties annotation and your 12-Factor Spring Boot apps will be configuration compliant.

4. Backing services and Spring Boot

The ability to treat backing services as attached resources has been baked into the Java language since its inception, so it would be difficult to violate this 12-Factor constraint even if a developer made a concerted effort to do so.

For example, all databases accessed through Java Database Connectivity (JDBC) require a URL and driver, which implicitly makes the database an attached resource. It would be impossible to perform JDBC or JPA in Java without the database being treated as a backing service. The same goes for NoSQL databases, Kafka queues and RESTful web services. If you code in Jakarta EE or Spring Boot, you're pretty much forced to treat all external resources as backing services, as per the 12-Factor guidelines.

5. Build, release and run

The suggestion that developers follow a strict build, release and run strategy seems somewhat self-evident. Ironically, this may also be one of the most commonly violated 12-Factor rules.

The idea here is that you should always build your code from your codebase. A release is a tagged build that is associated with a versioned configuration file. And it is that combination of the tagged build and the versioned configuration data that you deploy and run on the server.

Code that runs on the server should not be updated to fix a bug. A configuration setting should not be tweaked at runtime to overcome a Java performance problem. All of the code that goes into deployment comes from the build, which is based on code that is versioned in a bare Git repo.

The configuration data must not change once it is paired with the build to create the release. If a change is needed, the team must go through the full build, release and run cycle again. There should be no shortcuts that violate the 12-Factor build, release and run principle.

6. Stateless Spring Boot processes

Java and Jakarta EE APIs have a number of classes and interfaces that implicitly add state to an application, not the least of which is the Servlet and JSP API's HttpSession object.

To achieve 12-Factor compliance, Spring Boot provides a replacement for the HttpSession called the Spring Session. This helps to externalize data that would otherwise be held statefully on an application server like Tomcat or Jetty. This is a prime example of how Spring Boot has provided additional APIs and re-implemented commonly used classes to ensure applications and microservices remain 12-Factor compliant.

Furthermore, the ease with which Spring Boot allows developers to externalize state in NoSQL databases such as Cassandra and MongoDB also helps to simplify the development of stateless microservices.

It must be noted that the responsibility to ensure a microservice runs as a stateless process also lies heavily on the software developer's shoulders. If a developer decides to hold user-state in an instance variable and not externalize that data in a shared resource, then there's nothing Spring or Docker or Kubernetes can do to allow that application to scale as a stateless process.

7. Port binding

Port binding is another 12-Factor principle that lies outside the purview of the Spring Boot framework. Instead, it is the Docker container that will map the internal port a Tomcat or Undertow server uses to a public one. In a clustered environment, it is the kubectl utility that will bind a Kubernetes Pod's port as a public service. Either way, the Spring Framework isn't responsible for the port-binding requirement.

Spring does provide the ability to change the port used internally by the packaged Spring Boot application, but it is Kubernetes or Docker that will take care of the external port binding that makes the cloud-native application publicly accessible.

8. Concurrency

According to the Twelve-Factor App methodology, cloud-native applications should be able to scale out horizontally to support high-volume, concurrent request-response cycles from clients. So long as a Spring Boot application is stateless, Kubernetes replica sets will take care of the creation of new pods with Docker containers that can handle increasing workloads concurrently.

9. Fast startups and shutdowns

The 9th principle of the Twelve-Factor App methodology is disposability, which insists that microservices should start up quickly and shut down gracefully.

To facilitate disposability, Spring Boot implements the lazy loading design pattern. It also performs smart-initializations to reduce the number of objects that are created as a cloud-native microservice starts. Furthermore, when developers use resource classes provided by the Spring framework, the inversion of control capabilities ensure that resources are gracefully terminated when a Kubernetes node is drained or a Docker container is taken offline.

10. Parity between environments

There will always be differences between development, user acceptance testing, pre-prod and the production environment. But the Twelve-Factor methodology insists that these environments be kept as similar as possible.

To facilitate environment parity, Spring Boot builds will create a runnable JAR file with an application server such as Tomcat embedded within. The same embedded Tomcat JAR file that is packaged inside a Docker container will be used in each of the different deployment environments. Since the same compiled code and application server is deployed to each environment, parity between environments is ultimately achieved.

Furthermore, Spring Profiles provide a simple way to define and configure properties that need to change from one environment to the next, allowing the developer to address differences between environments that will inevitably occur.

11. Logs as event streams

The Twelve-Factor App insists that logs be treated as event streams.

All of the standard Java logging frameworks used by Spring Boot write their data to an event stream that gets saved to a common directory on the Kubernetes node that runs Docker containers. These log files are then easily consumed by Kubernetes DaemonSets such as FluentD or Logstash. These DaemonSets then stream the logs to tools such as Elasticsearch and Logstash for consumption.

As long as you use the Java logging frameworks that come standard with the framework, you can rest assured that you have a 12-Factor compliant Spring Boot app when it comes to log consumption.

12. Admin processes management

An application will often need to run administrative processes that are not directly tied to the request-response cycle that handles client-server interactions. Per the Twelve-Factor App, the code to implement these processes should not be placed in a separate codebase. Admin processes should be packaged as part of a standard, version-controlled build, and the processes themselves should execute in the same runtime environment that the application uses.

It is very easy to add support for admin processes within a cloud-native Spring Boot app. The Spring Batch project makes it easy to add support for jobs that run once and exit. Furthermore, any Git repository can be configured to include folders that allow for the addition of scripts that can be run in an REPL shell when required.

There is no definitive standard for the development of cloud-native microservices, but the Twelve-Factor App methodology comes close. If you are a Java developer and want to create 12-Factor apps, Spring Boot will help your team maintain cloud-native compliance.

Dig Deeper on Front-end, back-end and middle-tier frameworks