Skip to content

Helidon 27 Tutorial: Build a Java Microservice with Virtual Threads

Learn how to build a Helidon 27 Java microservice from scratch, configure HTTP routes, use virtual threads, run the development loop, and create a custom runtime image.

Helidon 27 Tutorial: Build a Java Microservice with Virtual Threads

On this page

Helidon 27 was released on September 22, 2026, and it changes the framework's relationship with Java by aligning its feature releases with the Java Development Kit release cycle. For developers, the practical starting point is a small HTTP service: Helidon 27 requires Java 27, uses virtual threads throughout its WebServer, and lets you define routes with ordinary Java code. This tutorial walks through creating that service, adding multiple endpoints, building it with Maven, and understanding what the new release changes before you use it in a larger project.

What changed in Helidon 27

Helidon is a Java framework for building standalone cloud-native applications and microservices. The 27 release is the first Helidon feature release following the Java feature-release cadence, so Helidon 27 targets Java 27 rather than maintaining the older independent versioning pattern. The framework's WebServer uses virtual threads, which are lightweight Java threads designed to make high-concurrency request handling practical without requiring a complicated reactive programming model. That distinction matters because you can write blocking-style Java code while the runtime handles concurrent requests using virtual threads.

Helidon 27 also changes how the project is organized. MicroProfile support has moved out of the core Helidon repository into a separate project, while extensions have their own release lifecycle. Declarative support is largely feature complete but remains a preview feature. For a first application, however, the core WebServer and routing APIs are enough to build a useful service without adopting those additional pieces.

Check the Java and Maven prerequisites first

You need Java 27 or newer and Maven 3.8 or newer for the Helidon 27 quickstart. Maven is the Java build tool that resolves dependencies, compiles source files, runs tests, and packages the application. Helidon also provides a command-line interface, or CLI, for generating projects, but the generated application itself remains an ordinary Maven project that you can open in an integrated development environment.

RequirementVersionWhy it matters
Java Development Kit27+Helidon 27 requires Java 27.
Maven3.8+Builds and packages the application.
Helidon27.0.0Provides the WebServer and routing APIs used here.

Check both tools before creating the project. The version output should show Java 27 or newer and Maven 3.8 or newer. If Java reports an older version, changing the Maven configuration alone will not fix the problem because the Helidon runtime itself requires the newer Java release.

java --version
mvn --version

Create a Helidon 27 project with the CLI

The Helidon CLI can generate the basic project structure so you do not have to assemble the Maven configuration by hand. The following command creates a Helidon SE-style quickstart application, where SE refers to Helidon's core programming model rather than the separate MicroProfile project.

helidon init --batch -Dflavor=se -Dapp-type=quickstart

The command creates a project directory containing the Maven configuration and Java source structure. Move into that directory before building it. If you do not want to install the CLI, Helidon also provides a project starter, but the CLI is useful during development because it includes a development loop that can rebuild and restart the application after source changes.

cd quickstart-se

At this point you have a complete starting project rather than an empty Maven directory. Keeping the generated project intact for the first run is useful because it gives you a known-good baseline before you start changing routes or dependencies.

Build and run the generated application

Build the project before changing any source code. Maven downloads the required Helidon components, compiles the Java classes, runs the project's build lifecycle, and creates the application artifact. The result should include a packaged quickstart application under the target directory.

mvn clean install

After the build succeeds, start the packaged application with Java.

java -jar target/quickstart-se.jar

The generated application exposes a greeting route. Open that route from a browser or another HTTP client and check that the service returns its greeting response. If the application starts but the route does not respond, first check the terminal for a port-binding error or another startup exception rather than changing the Java source immediately.

Replace the greeting with a simple application API

The useful part of Helidon begins when you define your own routing. A route connects an HTTP method and path to a handler, which is the Java code that processes the request and creates the response. The WebServer builder lets you configure these routes directly, so a small service does not need a large controller hierarchy.

For a minimal example, create a main class that starts the WebServer and registers two GET endpoints. A GET request normally asks a server to return a resource without changing server-side state. The first endpoint returns a health response, while the second returns a simple product message.

package example;

import io.helidon.webserver.WebServer;

public class Main {
    public static void main(String[] args) {
        WebServer.builder()
                .routing(routing -> routing
                        .get("/health", (req, res) -> res.send("ok"))
                        .get("/products", (req, res) -> res.send("Product API is running")))
                .build()
                .start();
    }
}

The important part is the routing block. Each get call associates a path with a handler, and the handler receives a request and response object. Calling res.send() writes the response body and completes the request. This is deliberately small, but the same routing mechanism can grow into a service with separate classes for larger pieces of application logic.

Add a path parameter instead of hard-coding every route

Real APIs usually need values supplied by the client, such as a product identifier. Helidon's routing system supports named path segments, allowing one route to match multiple resource identifiers. A route such as /products/{id} can therefore handle different product IDs without creating a separate route for every value.

For a production application, the handler would normally read the matched value, validate it, query a data source, and serialize a structured response. The important design point is to keep that business logic separate from the routing declaration once the application grows. Helidon also provides an HttpService interface for grouping related handlers under a common path prefix.

Use virtual threads without rewriting the application as reactive code

Virtual threads are one of the most significant implementation details behind Helidon 27's programming model. A traditional platform thread is comparatively expensive, so applications that need to handle large numbers of concurrent operations have often used asynchronous APIs or reactive programming to avoid blocking threads. A virtual thread is much lighter and can be created in large numbers, allowing Java applications to use straightforward blocking operations while still handling substantial concurrency.

Helidon 27 uses virtual threads throughout its WebServer. That does not automatically make every application fast, because database latency, network calls, CPU-heavy work, serialization, and poor application design can still dominate response time. What changes is the programming model available to you: a handler can perform an ordinary blocking operation without forcing you to redesign the entire service around callbacks or reactive pipelines. The benefit is most relevant to services that spend meaningful time waiting for external resources.

Keep the distinction clear: virtual threads make blocking I/O easier to scale, but they do not make CPU-intensive work execute faster. A calculation that consumes a processor for a long time still consumes processor time whether it runs on a virtual thread or a platform thread.

Try Helidon's development loop while editing

Once the first application runs, repeatedly stopping Maven, rebuilding the project, and restarting Java becomes tedious. The Helidon CLI includes a development loop that watches the project, recompiles it, and restarts the application as you make changes. Start it from the project directory with the following command.

helidon dev

Change the response text in one of your route handlers and save the file. The development loop should rebuild and restart the application, allowing you to test the change without manually repeating the complete build-and-run sequence. This is especially useful when experimenting with routing because the feedback cycle becomes much shorter.

Understand the Helidon 27 trade-offs before using it

Helidon 27 is not simply a drop-in update for every existing Helidon application. The minimum Java version has moved to 27, MicroProfile is no longer part of the core Helidon 27 release, and some extensions now follow independent release cycles. Those changes matter more to an existing production project than they do to a new quickstart. Before upgrading an older application, check its Helidon modules and extension dependencies rather than changing the parent version and assuming everything will compile.

There is another practical limitation worth knowing about this release: Helidon's Maven documentation states that GraalVM Native Image is not supported in Helidon 27. If your deployment depends on native-image builds, that changes the upgrade calculation even if the application's Java code itself needs few modifications. Helidon 27 does provide support for creating a custom Java runtime image with the Java Development Kit's jlink tool, which assembles only the required Java modules and application runtime components.

Build a smaller runtime image when you are ready to deploy

The jlink tool can produce a custom Java runtime containing the modules needed by your application. Helidon provides a Maven profile for this workflow, so you do not have to construct the runtime image manually. After your application works normally, run the following build command.

mvn package -Pjlink-image

The generated runtime image is placed under the target directory. It contains the application, its runtime dependencies, and the required Java modules, so the deployment does not need a complete general-purpose Java installation. Helidon also includes an ahead-of-time cache in the custom image by default, which can increase the image size while providing startup-related optimizations. If image size matters more than that cache, Helidon's documentation provides a Maven property for disabling it.

mvn package -Pjlink-image -Djlink.image.aotCache=false

This is a deployment optimization rather than a requirement for learning Helidon. Get the normal JVM build working first, then measure the startup and packaging characteristics of the custom image before deciding whether the additional build step belongs in your deployment pipeline.

What to build next with Helidon 27

The small service above gives you the core pieces of Helidon 27: a Java 27 runtime, a Maven project, a WebServer, HTTP routing, and a development loop. From there, the next useful step is to replace the hard-coded response with application data and add configuration, health checks, metrics, security, or an outbound WebClient request as the service requires. Helidon 27's documentation also provides dedicated guides for configuration, database access, OpenID Connect, tracing, metrics, WebClient, Gradle, custom runtime images, and native-image-related deployment considerations.

For a new project, start with the plain WebServer model and keep the first service small enough that you can see exactly how requests enter the application and leave it. Once that path is clear, add the framework features your service actually needs. For an existing Helidon application, check the Java 27 requirement and the changes around MicroProfile and extensions before scheduling an upgrade, because those are more likely to affect the project than the basic route-handling code shown here.

Muhammad Saleem profile photo

Written by

Muhammad Saleem

I’m Muhammad Saleem, a web developer and the owner of TechWare House, a software house focused on practical web and software solutions. With over 14 years of experience, I’ve built and managed hundreds of websites and custom , PHP/MySQL, Python, Django applications. I share hands-on insights about web development, software, technology, and digital solutions on WizTechnoz.com

45 posts published

All posts by this author

0 Comments

No comments yet. Be the first to share your thoughts.

Join the conversation

Log in or create a free account to leave a comment. You can edit or delete your own comments any time.