Back to Blog
Tutorial10 min read2026-07-04

How to Deploy a Vert.x Reactive App

Deploy an Eclipse Vert.x reactive application to production: building a fat JAR, tuning the event loop for containers, handling backpressure, health checks, and connecting a managed database with the reactive Postgres client.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Eclipse Vert.x is a toolkit, not a framework, which is part of why it's so good at high-concurrency workloads. It runs on a small number of event-loop threads and never blocks them, letting a single instance handle tens of thousands of connections. Deploying it well means respecting that model: sizing threads correctly, keeping blocking work off the event loop, and configuring health checks that reflect actual readiness.

The app structure

A typical Vert.x app deploys one or more verticles. Here's a minimal HTTP server verticle that reads its port from the environment:

public class MainVerticle extends AbstractVerticle {
  @Override
  public void start(Promise<Void> startPromise) {
    int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
    Router router = Router.router(vertx);
    router.get("/health").handler(ctx -> ctx.response().end("OK"));
    router.get("/api/items").handler(this::listItems);

    vertx.createHttpServer()
      .requestHandler(router)
      .listen(port, "0.0.0.0")
      .onSuccess(s -> startPromise.complete())
      .onFailure(startPromise::fail);
  }
}

Binding to 0.0.0.0 is non-negotiable in a container, otherwise the server is unreachable from outside the pod.

Reactive database access

Vert.x has its own non-blocking Postgres client. Use it instead of JDBC so you don't block event-loop threads waiting on the database:

PgConnectOptions options = PgConnectOptions.fromUri(System.getenv("DATABASE_URL"));
Pool pool = PgBuilder.pool()
  .connectingTo(options)
  .with(new PoolOptions().setMaxSize(8))
  .using(vertx)
  .build();

The fromUri helper parses a standard postgresql://user:pass@host:5432/db URL, which is exactly the format managed databases provide. Keep the pool size modest. Vert.x multiplexes, so you rarely need a large pool; eight connections per instance is plenty for most APIs.

Building a fat JAR

Vert.x apps deploy as a single executable JAR. With Maven Shade or the Gradle Shadow plugin you produce a fat JAR that includes everything:

./gradlew shadowJar

Then a clean two-stage Dockerfile:

FROM gradle:8.7-jdk21 AS build
WORKDIR /app
COPY . .
RUN gradle shadowJar --no-daemon

FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
COPY --from=build /app/build/libs/*-all.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", \
  "-Dvertx.options.eventLoopPoolSize=2", "-jar", "app.jar"]

Sizing the event loop for containers

By default Vert.x sets the event-loop pool to 2 * available processors. In a container with a CPU limit, the JVM may misread the number of available cores. Set eventLoopPoolSize explicitly to match your container's CPU allocation. On a 1-CPU tier, two event-loop threads is a reasonable choice; over-provisioning threads just adds context-switching overhead.

For any blocking work (legacy JDBC calls, heavy CPU computation), use executeBlocking or a dedicated worker verticle so you never stall the event loop. A blocked event loop is the single most common cause of mysterious latency spikes in Vert.x deployments.

Health checks and backpressure

Vert.x ships a health-check handler in vertx-health-check. Wire a readiness check that actually pings the database pool, so the orchestrator doesn't route traffic to an instance whose database is unreachable:

HealthChecks hc = HealthChecks.create(vertx);
hc.register("db", promise ->
  pool.query("SELECT 1").execute()
    .onSuccess(r -> promise.complete(Status.OK()))
    .onFailure(t -> promise.complete(Status.KO())));
router.get("/ready").handler(HealthCheckHandler.createWithHealthChecks(hc));

For streaming endpoints, lean on Vert.x's built-in backpressure: ReadStream.pause()/resume() and the Pipe API prevent a fast producer from overwhelming a slow consumer. This is one of the framework's strongest features and a reason to use it over a thread-per-request stack.

Deploying on PandaStack

With a Dockerfile committed, the deploy is a git connection:

  1. 1Connect your GitHub repo as a container app in the dashboard.
  2. 2PandaStack builds with rootless BuildKit in an ephemeral Job pod and pushes to Google Artifact Registry, then deploys via Helm.
  3. 3Provision a managed PostgreSQL instance; DATABASE_URL is injected automatically and PgConnectOptions.fromUri picks it up.
  4. 4Watch live build and app logs to confirm both the HTTP server bound and the readiness check turned green.
ConcernSetting
Bind address0.0.0.0 (required)
Portread PORT env
Event-loop threadsmatch CPU tier
DB pool size4-8 per instance
ReadinessDB ping, not just process up

Free-tier apps scale to zero on spot nodes inside a gVisor sandbox, so a low-traffic Vert.x service costs nothing while idle and cold-starts on the next request. For a reactive app that's typically handling bursty traffic, scale-to-zero is a good economic fit.

Verifying

curl https://your-app.pandastack.app/ready
curl https://your-app.pandastack.app/api/items

Attach a custom domain for automatic SSL, and use the server-side metrics view for latency and throughput without adding any instrumentation to your code.

References

  • Vert.x core documentation: https://vertx.io/docs/vertx-core/java/
  • Vert.x reactive Postgres client: https://vertx.io/docs/vertx-pg-client/java/
  • Vert.x health checks: https://vertx.io/docs/vertx-health-check/java/
  • Gradle Shadow plugin: https://gradleup.com/shadow/
  • JVM container CPU/memory ergonomics: https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html

Vert.x rewards a deployment that respects its event-loop model. Get the thread sizing and non-blocking database access right and a single small instance goes a long way. Try the full flow, build to live URL with an auto-wired database, on PandaStack's free tier at https://dashboard.pandastack.io

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also