Wenn Java-Entwickler an Microservices denken, fallen zuerst Quarkus und Micronaut. Doch seit Version 4 hat Oracle’s Helidon einen bemerkenswerten Wandel vollzogen: Es ist das erste Java-Webframework, das vollständig auf Virtual Threads (Project Loom) basiert — und bietet damit eine erfrischende Alternative zum reaktiven Programmiermodell.
Dieser Artikel zeigt dir den Einstieg in Helidon 4.5 und erklärt, warum der „Blocking-Code auf Virtual Threads“-Ansatz das Leben von Microservice-Entwicklern einfacher macht.
Was ist Helidon?
Helidon (griechisch für Schwalbe — ein kleiner, wendiger Vogel) gibt es in zwei Flavors:
| Flavor | Beschreibung |
|---|---|
| Helidon SE | Funktionaler, expliziter Stil ohne Framework-Magie. Kleinster Footprint (~34 MB native). |
| Helidon MP | MicroProfile-kompatibel mit CDI, JAX-RS, JSON-P/B. Für Jakarta-EE-erfahrene Teams. |
Beide teilen sich den Helidon WebServer als Kern und unterstützen Java 21+ sowie GraalVM Native Image. Der aktuelle Release ist Helidon 4.5.0 (Juni 2025).
Der Clou: Virtual Threads statt Reactive
Das reaktive Programmierparadigma (Project Reactor, RxJava, Mutiny) hat ein echtes Problem: Der Code wird schwer lesbar, Stacktraces werden unbrauchbar, und Debugging wird zur Qual. Helidon 4 löst das anders: Es verwendet Java Virtual Threads, die bei I/O-Operationen automatisch vom JVM-Scheduler suspendiert werden, ohne Plattform-Threads zu blockieren.
Das Resultat: Du schreibst ganz normalen, blockierenden Code — und die JVM macht ihn hochskalierend.
<em>// In Spring WebFlux (reaktiv):</em>
return userRepository.findById(id)
.flatMap(user -> orderService.findOrders(user))
.map(orders -> new UserWithOrders(user, orders));
<em>// In Helidon (blockierend auf Virtual Threads):</em>
var user = userRepository.findById(id);
var orders = orderService.findOrders(user);
return new UserWithOrders(user, orders);
Code-Sprache: JavaScript (javascript)
Beide Varianten skalieren auf Millionen gleichzeitiger Verbindungen — aber nur eine liest sich wie normaler Java-Code.
Quickstart: ein REST-Endpunkt mit Helidon SE
Projekt mit der Helidon CLI erstellen:
curl -L -O https://helidon.io/cli/latest/darwin/helidon
chmod +x helidon && sudo mv helidon /usr/local/bin/
helidon init --batch -Dflavor=se -Dapp-type=quickstart
cd quickstart-se && mvn clean package -Pjre
java -jar target/quickstart-se.jar
Code-Sprache: JavaScript (javascript)
Die generierte Main.java:
import io.helidon.webserver.WebServer;
import io.helidon.webserver.http.HttpRouting;
public class Main {
public static void main(String[] args) {
WebServer.builder()
.port(8080)
.addRouting(HttpRouting.builder()
.get("/greet", (req, res) -> res.send("Hallo Helidon!"))
.get("/greet/{name}", (req, res) -> {
String name = req.path().pathParameters().get("name");
res.send("Hallo " + name + "!");
})
)
.build()
.start();
}
}
Code-Sprache: JavaScript (javascript)
Noch simpler geht’s kaum. Der Server startet in ~0,6 Sekunden, und jede Anfrage läuft auf einem eigenen Virtual Thread.
Eine kleine REST-API mit Services
So sieht eine vollständige Todo-API mit Dependency Injection (Helidon Inject) und JSON-Serialisierung aus:
<em>// Todo.java — Modell</em>
public record Todo(String id, String task, boolean done) {}
<em>// TodoService.java — Business-Logik</em>
@Singleton
public class TodoService {
private final Map<String, Todo> todos = new ConcurrentHashMap<>();
public TodoService() {
add(new Todo("1", "Helidon lernen", false));
}
public List<Todo> list() {
return List.copyOf(todos.values());
}
public Todo add(Todo todo) {
todos.put(todo.id(), todo);
return todo;
}
}
Code-Sprache: PHP (php)
<em>// TodoRoutes.java — HTTP-Endpunkte</em>
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;
import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObject;
@Singleton
public class TodoRoutes implements HttpService {
private final TodoService service;
@Inject
public TodoRoutes(TodoService service) {
this.service = service;
}
@Override
public void routing(HttpRules rules) {
rules.get("/todos", this::getAll)
.post("/todos", this::create);
}
private void getAll(ServerRequest req, ServerResponse res) {
var json = service.list().stream()
.map(t -> Json.createObjectBuilder()
.add("id", t.id())
.add("task", t.task())
.add("done", t.done())
.build())
.collect(Json.createArrayBuilder(), JsonArrayBuilder::add, (a, b) -> {})
.build();
res.header("Content-Type", "application/json").send(json);
}
private void create(ServerRequest req, ServerResponse res) {
JsonObject body = req.content().as(JsonObject.class);
Todo todo = service.add(new Todo(
UUID.randomUUID().toString(),
body.getString("task"),
false
));
res.status(201).send(todo);
}
}
Code-Sprache: JavaScript (javascript)
Virtual-Thread-Architektur unter der Haube
Helidon 4’s Server (ursprünglich unter dem Codenamen „Níma“ entwickelt) verwendet folgendes Thread-Modell:
- Socket-Listener laufen auf Plattform-Threads (ein Thread pro Server-Socket)
- HTTP/1.1: Ein Virtual Thread pro Verbindung (bearbeitet Routing + alle Requests)
- HTTP/2: Ein Virtual Thread pro Stream — jeder Stream bekommt Routing auf einem eigenen Thread
- Der JVM-Scheduler multiplexiert Millionen Virtual Threads auf wenige OS-Threads
Das bedeutet: Du kannst Code wie Thread.sleep() oder blockingRepository.findById() ganz normal aufrufen, ohne Angst vor Thread-Pool-Erschöpfung. Der Virtual Thread wird suspendiert, der Carrier-Thread arbeitet weiter an einem anderen Virtual Thread, und sobald das I/O bereit ist, läuft dein Code weiter.
Performance & GraalVM Native Image
Helidon SE glänzt mit beeindruckenden Werten:
| Metrik | JVM | Native Image |
|---|---|---|
| Speicher (RSS) | ~70 MB | ~34 MB |
| Startup-Zeit | 0,6 s | 0,06 s |
| Artefaktgröße | 333 MB | 38 MB |
Mit GraalVM Native Image lassen sich sub-100ms Startup-Zeiten erreichen — ideal für Serverless und Auto-Scaling. Das native Image baust du mit:
mvn package -Pnative
Helidon SE vs. Helidon MP: Wann welchen Flavor?
- Helidon SE eignet sich für Teams, die maximale Kontrolle und minimalen Footprint wollen. Keine Annotation-Magie, kein CDI-Overhead, direkte APIs.
- Helidon MP ist ideal für Teams mit Jakarta-EE-Hintergrund oder wenn MicroProfile-Standards (Health, Metrics, OpenAPI, Config) gefordert sind.
Maven BOM für eigene Projekte
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.helidon</groupId>
<artifactId>helidon-bom</artifactId>
<version>4.5.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Code-Sprache: HTML, XML (xml)
Fazit
Helidon 4 beweist, dass Java-Microservices nicht reaktiv sein müssen, um hochskalierend zu sein. Virtual Threads erlauben sauberen, debuggaren Code mit vergleichbarer Performance. Besonders Helidon SE besticht durch seine Einfachheit: Keine Reflexion, keine Annotation-Prozessoren, keine Framework-Magie — nur Java, so wie du es kennst.
Für den nächsten Microservice solltet ihr Helidon definitiv auf der Shortlist haben.