Spring Boot made the Java application a single executable JAR, and most teams stop there: one JAR, one VM, one systemd unit, one person who restarts it. Making that JAR highly available and elastic is supposed to be the hard part. This post follows a Java developer doing it on MassiveGRID PaaS, which is built on Virtuozzo Application Platform, formerly Jelastic, with the Auto-Scalable Spring Boot Cluster package, in an afternoon.

The application is a REST API behind a fleet-tracking product for a company we will call Gantry Logistics: a Spring Boot 3 service with a PostgreSQL database, receiving vehicle telemetry that peaks with the morning rush. The Auto-Scalable Spring Boot Cluster deploys the JAR on two or more Java Engine nodes behind an NGINX load balancer, with scaling triggers and built-in SSL.

What Java Engine nodes are

The package does not run the JAR on a full Tomcat or WildFly container. It uses Java Engine nodes: lightweight containers with a JDK and a process runner, designed for executable JARs. There is no application server to configure because Spring Boot embeds its own. The cluster starts with two such nodes and a sample application already running, so the developer can see the balancer, the health checks and the scaling work before she deploys anything of her own. The JDK is a current LTS by default and switchable from the dashboard to match the build's target.

Deploying the JAR

Two routes. The quick one is to upload the built JAR through the Deployment Manager, which deploys it to every node in the layer. The one Gantry keeps is Git: connect the repository, and the platform provisions a Maven build node that builds on push and deploys the artefact to the Java Engine layer. Deployment to a multi-node layer is sequential by default: node one is taken out of the balancer, restarted on the new JAR, given a pause, then node two. The JVM's startup time is the only downtime per node, and the other node serves throughout.

Configuration follows Spring Boot's own conventions. Database credentials and the telemetry ingestion keys are environment variables on the layer, which Spring's relaxed binding reads as properties; nothing is baked into the JAR. The JVM heap follows the cloudlet allocation, because the platform's Java containers set memory options from the container's limit, so the developer does not hard-code an -Xmx.

Making the balancer smart with Actuator

NGINX in front of the layer distributes requests and checks node health. Spring Boot Actuator's /actuator/health endpoint is the right health check: it reports the application as down if the database connection pool is exhausted or a dependency is unreachable, which a plain TCP check would miss. The developer enables Actuator, restricts the endpoint to the internal network, and points the balancer's health check at it. A node whose health endpoint returns 503 stops receiving traffic until it recovers, with no operator involved. The developer also exposes a separate readiness probe that reports unhealthy during the first seconds after start-up while caches warm, so a freshly scaled node receives traffic only once it can answer quickly.

Scaling on the morning rush

Telemetry arrives in a wave from 06:30 as drivers start their vehicles. The developer sets horizontal scaling triggers on the Java Engine layer: add one node when average CPU exceeds 65% for 2 minutes, up to six; remove one when below 25% for 15 minutes, down to two. New nodes are created from the layer's master in stateful mode, so they carry the deployed JAR and start serving after JVM start-up, joining the balancer automatically. Vertical scaling handles the rest: each node's cloudlet limit is 24 (a cloudlet is 128 MiB of RAM plus 400 MHz of CPU), and the JVM grows into it during the rush and shrinks after.

TimeRequests/sNodesCloudlets per node
04:004026
06:45900418
08:301,400620
11:00500312
22:006026

State, and keeping it out of the nodes

Nodes behind a balancer must be interchangeable. Gantry's API is stateless by design: JWT authentication, no server-side sessions. For applications that do use HTTP sessions, the package offers sticky sessions on NGINX, or Spring Session with a Redis node so any node can serve any user, which is the better answer when nodes are added and removed by triggers. The PostgreSQL database is the marketplace's Primary-Secondary Cluster in the same environment group, reached over the internal network, with connection pool sizes set so six nodes do not exhaust the primary's connection limit.

What the afternoon produced

Logs and metrics follow the same principle as configuration. Spring Boot logs to standard output, which the platform's node log viewer captures per node, and Actuator's Prometheus endpoint is scraped by the team's existing Grafana over the internal network, so the six-node peak and the two-node night are visible on the same dashboard as the request rate.

By 17:00 the JAR that had lived on one VM ran on two to six nodes, deployed from Git with sequential restarts, health-checked by Actuator, scaled by CPU and billed per cloudlet-hour on what the JVMs actually used: roughly 45 cloudlets an hour averaged across the day, about $110 a month before discounts at MassiveGRID's published $0.003372. The 2 am restart became a page the developer has not received since.

Frequently Asked Questions

Should I use the Spring Boot Cluster, the Fat JAR or the Thin JAR package?

The Cluster is the production shape: balancer, several nodes, triggers. The Fat JAR and Thin JAR packages run a single Java Engine node for development or small internal services; Thin JAR downloads dependencies at start from a repository such as Nexus, which keeps the artefact small. Start with a single node and clone or scale into the cluster when traffic arrives.

How does the JVM know how much memory it has?

The platform's Java containers set JVM memory options from the container's cloudlet limit, and modern JDKs are container-aware. Leave heap flags unset and adjust the cloudlet limit; the JVM follows.

Can I deploy a WAR instead?

Yes, but to Tomcat or TomEE rather than Java Engine nodes; the Tomcat/TomEE Cluster package is the equivalent with session replication. Executable JARs on Java Engine are lighter and are the recommended form for Spring Boot.

Your JAR, load-balanced by tonight

The Auto-Scalable Spring Boot Cluster runs your executable JAR on Java Engine nodes behind NGINX on MassiveGRID PaaS, deployed from Git, scaled by triggers and billed per cloudlet-hour. Free 14-day trial, no credit card.

Spring Boot Cluster on MassiveGRID PaaS

Further Reading