Helidon SE 4.x Upgrade Guide

In Helidon 4.x we have made some major changes to Helidon. Reactive engine has been removed. APIS and implementations are rewritten in "blocking" paradigm. This guide will help you upgrade a Helidon SE 3.x application to 4.x.

Java 21 Runtime

Java 17 is no longer supported in Helidon 4. Java 21 or newer is required. Please follow the instructions in Prerequisites for proper installation.

Helidon 4 no longer uses Netty. Helidon SE is now running on Helidon WebServer which is based on virtual threads technology, available in Java 21.

Programming paradigm

Helidon SE has changed from an asynchronous style API to an imperative/blocking style API that is optimized for use with virtual threads. Currently, there is no compatibility API available

Server Initialization and Start Up

In Helidon 1.x-3.x you started a server like this:

Start Helidon SE 3.x Server
static Single<WebServer> startServer() {
    Config config = Config.create();

    WebServer server = WebServer.builder(createRouting(config))
            .config(config.get("server"))
            .addMediaSupport(JsonpSupport.create())
            .build();

    Single<WebServer> webserver = server.start(); 

    webserver.thenAccept(ws -> { 
                System.out.println("WEB server is up! http://localhost:" + ws.port() + "/greet");
                ws.whenShutdown().thenRun(() -> System.out.println("WEB server is DOWN. Good bye!"));
            })
            .exceptionallyAccept(t -> { 
                System.err.println("Startup failed: " + t.getMessage());
                t.printStackTrace(System.err);
            });

    return webserver;
}
Copied
  • Server is started in an asynchronous way. A Single object is returned.
  • Wait for the Server to start and print the message in an asynchronous way.
  • Gracefully handle exceptions if they occur during the initialization process.

Since Helidon SE in 3.x was reactive, during the start a Single object is returned, the server has been started in asynchronous way. We have to use reactive methods like thenAccept to wait for the server to start and then to perform the desired action. The exception handling should also be done in reactive way using the corresponding method.

In Helidon 4.x asynchronous programming is no longer required so the server startup is much simpler:

Start Helidon SE 4.x Server
public static void main(String[] args) {

    Config config = Config.create();
    Config.global(config);

    WebServer server = WebServer.builder() 
            .config(config.get("server"))
            .routing(Main::routing)
            .build()
            .start(); 

    System.out.println("WEB server is up! http://localhost:" + server.port() + "/greet"); 
}
Copied
  • Configure the Server.
  • Start the Server. No reactive objects returned.
  • Print a message when the Server is started.

Just create it, configure it, and wait for it to start. If any exceptions happen, they are handled the traditional way using available language constructions.

Server Features and Media Support Discovery

In previous versions of Helidon you had to explicitly register WebServer features (register(MetricsSupport.create())) and explicitly add media support (addMediaSupport(JsonpSupport.create())). In Helidon 4 the default behavior is to automatically discover these components from the classpath. So all you need to do is add the dependencies to your pom.xml and optionally add configuration to customize them.

If you want full control using the API, you still have that option.

For more information see:

Routing Configuration

In Helidon 1.x-3.x the routing config was done the following way:

Routing in Helidon SE 3.x Server
private static Routing createRouting(Config config) {

    MetricsSupport metrics = MetricsSupport.create(); 
    HealthSupport health = HealthSupport.builder()
            .addLiveness(HealthChecks.healthChecks())
            .build();

    GreetService greetService = new GreetService(config); 

    return Routing.builder()
            .register(health) 
            .register(metrics)
            .register("/greet", greetService) 
            .build();
}
Copied
  • Create and configure Metrics and Heath support.
  • Create a regular Helidon Service.
  • Register Metrics and Heath support as Helidon Services.
  • Register the regular Greeting service.

Services are created and assigned to the desired path. Observability and other features are being created as usual Helidon services, available as part of the framework. User-defined services are also registered the same way.

In Helidon 4, the routing is configured the following way:

Start Helidon SE 4.x Server
static void routing(HttpRouting.Builder routing) {
    routing.register("/greet", new GreetService()); 
}
Copied
  • Register Greeting service as in previous versions of Helidon.

As described previously, the Metrics and Health features will be discovered automatically as long as you have added the dependencies for them to your project.

If you wanted to add these features to the server programmatically you would do so using WebServer.builder().addFeature() method.

Feature encapsulates a set of endpoints, services and/or filters. It is similar to HttpService but gives more freedom in setup. Main difference is that a feature can add Filters and it cannot be registered on a path. Features are not registered immediately—each feature can define a Weight or implement Weighted to order features according to their weight. Higher-weighted features are registered first. This is to allow ordering of features in a meaningful way (e.g. Context should be first, Tracing second, Security third etc).

Services

There are also significant changes in Helidon Service.

In prior versions, a service looks this way:

Helidon SE 3.x Service
public class GreetService implements Service {

    @Override
    public void update(Routing.Rules rules) { 
        rules
                .get("/", this::getDefaultMessageHandler)
                .get("/{name}", this::getMessageHandler)
                .put("/greeting", this::updateGreetingHandler);
    }

    private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { 
        sendResponse(response, "World");
    }

    // other methods omitted
}
Copied
  • Use update() method to set up routing.
  • Handle a Request and return a Responce.

In Helidon 4, the same service:

Helidon SE 4.x Service
public class GreetService implements HttpService { 

    @Override
    public void routing(HttpRules rules) { 
        rules.get("/", this::getDefaultMessageHandler)
                .get("/{name}", this::getMessageHandler)
                .put("/greeting", this::updateGreetingHandler);
    }

    private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { 
        sendResponse(response, "World");
    }

    private void getMessageHandler(ServerRequest request, ServerResponse response) {
        // ...
    }

    private void updateGreetingHandler(ServerRequest request, ServerResponse response) { 
        // ...
    }
}
Copied
  • Implement HttpService for the GreetingService.
  • Use routing(HttpRules rules) to set up routing.
  • Handle a Request and return a Responce.

Helidon 4 introduced HttpService that should be implemented in order to process HTTP requests. To set up routing, the method routing(HttpRules rules) should now be used. It receives HttpRules object with routes description.

ServerRequest and ServerResponse are now in the io.helidon.webserver.http package;

Http.Status is now io.helidon.http.Status

These changes make Helidon 4 incompatible with previous versions.

Learn more about HttpService and Routing at Helidon SE WebServer

Other Significant Changes

Media Support

Media support has moved from the io.helidon.media Java package to io.helidon.http.media and has new dependency coordinates. For example:

<dependency>
    <groupId>io.helidon.http.media</groupId>
    <artifactId>helidon-http-media-jsonp</artifactId>
</dependency>
Copied

In Helidon 4 media support is discovered by default, so you simply need to add the dependency. You no longer need to explicitly add media support using the WebServer builder.

Media support no long transitively brings the Jakarta API dependencies. So you might need to add these explicitly. For example:

<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
</dependency>
Copied

Testing

There is a new testing framework for Helidon SE.

<dependency>
    <groupId>io.helidon.microprofile.testing</groupId>
    <artifactId>helidon-microprofile-testing-junit5</artifactId>
    <scope>test</scope>
</dependency>
Copied

Find more information, see Helidon SE testing

Observability

Observability features of Helidon have now moved to different package. For Health and Metrics please use:

<dependencies>
    <dependency>
        <groupId>io.helidon.webserver.observe</groupId>
        <artifactId>helidon-webserver-observe-health</artifactId>
    </dependency>
    <dependency>
        <groupId>io.helidon.webserver.observe</groupId>
        <artifactId>helidon-webserver-observe-metrics</artifactId>
    </dependency>
</dependencies>
Copied

Observability has new endpoints. See them here.

For System Metrics, please use:

<dependency>
    <groupId>io.helidon.metrics</groupId>
    <artifactId>helidon-metrics-system-meters</artifactId>
</dependency>
Copied

By default, Observability features are discovered automatically if you add the above dependencies. If you choose to add them programmatically (using addFeature) you will need to add the following dependency:

<dependency>
    <groupId>io.helidon.webserver.observe</groupId>
    <artifactId>helidon-webserver-observe</artifactId>
</dependency>
Copied

Metrics has changed significantly in Helidon 4. See Helidon SE Metrics for more information.

Security

  • Changed modules:

    • helidon-security-integration-jersey moved to the module helidon-microprofile-security

    • helidon-security-integration-jersey-client moved to the module helidon-microprofile-security

    • helidon-security-integration-grpc was removed

    • helidon-security-integration-webserver moved to the module helidon-webserver-security

  • Significant class name changes:

    • OidcSupport renamed to OidcFeature

    • WebSecurity renamed to SecurityFeature

  • Other:

    • SynchronousProvider removed - SynchronousProvider usage is no longer needed, since all security providers are synchronous.

Global Configuration

The global configuration represents a single instance of the Config class, which is implicitly employed by certain Helidon components. Furthermore, it offers a handy approach for your application to access configuration information from any part of your code.

It is recommended that you explicitly initialize global configuration before using any Helidon components:

Config config = Config.create();  // Uses default config sources
Config.global(config);
Copied

You can then utilize the global configuration for easy retrieval of your application’s configuration:

Config config = Config.global();
Copied

More information at Helidon SE Config.

Logging

The class LogConfig has moved to the io.helidon.logging.common Java package.

The Helidon console handler has changed from io.helidon.common.HelidonConsoleHandler to io.helidon.logging.jul.HelidonConsoleHandler.

If you use this handler in your logging.properties you will need to update it and add the following dependency:

<dependency>
    <groupId>io.helidon.logging</groupId>
    <artifactId>helidon-logging-jul</artifactId>
    <scope>runtime</scope>
</dependency>
Copied

Conclusion

Please proceed to Helidon SE Introduction to find more information and documentation about each module.

Also, the Helidon examples are a good resource for seeing how things are done in Helidon 4.