Search

Dark theme | Light theme

February 20, 2025

Helidon SE Helpings: Serving Observe Endpoints On Different Port

When you enable the /observe endpoints you can configure them to be served on a different port than the application. By default the endpoints are available on the same port as the application. But you can define an extra named socket with another port number in the configuration of the WebServer instance. And in the configuration of the ObserveFeature instance you can define the socket name that should be used for the observe endpoints.

You can use configuration properties or code to configure the extra socket.

Using configuration

In the following example configuration properties file you see how to configure the extra socket and the observe feature to use the extra socket. A new socket with the name observe is defined and port 8081 is used by defining the property prefixed with server.sockets. The value is a list so you have to use a list definition for the property names name and port. Next this socket is used in the configuration of the observe feature by assigning the name to the server.features.observe.sockets property.

# File: application.properties
...
# Configure new socket for observe endpoints.
# Set the name of the new socket to observe.
server.sockets.0.name=observe
# Set the port for the new socket to 8081.
server.sockets.0.port=8081

# Configure the observe feature to use the observe socket.
server.features.observe.sockets.0=observe

# Set port for default socket.
server.port=8080
...

In your application code you can use the configuration to create a WebServer instance:

// File: Main.java
...
import io.helidon.config.Config;
import io.helidon.webserver.WebServer;
...
  public static void main(String[] args) {
    ...
    Config config = Config.create();

    WebServer server =
        WebServer.builder()
            ...
            // Read configuration properties.
            .config(config.get("server"))
            .build()
            .start();
  }
...

Using application code

Alternatively you can write the configuration in your application code. In the following example you can see how to create a WebServer instance using the configuration. To add a new socket definition you can use the WebServer.Builder.putSocket() method. Next you can use the ObserveFeature.Builder.sockets() method to connect the observe feature to the new socket.

// File: Main.java
...
import io.helidon.webserver.observe.ObserveFeature;
import io.helidon.webserver.observe.health.HealthObserver;
import io.helidon.webserver.WebServer;
...
  public static void main(String[] args) {
    ...
    // Create health check observer and enable details.
    HealthObserver healthObserver =
        HealthObserver.builder()
            // Show details in health endpoint.
            .details(true)
            .build();

    WebServer server =
        WebServer.builder()
            ...
            // Set default port.
            .port(8080)
            // Create a new socket with name "observe"
            // to run on port 8081.
            .putSocket("observe", socket -> socket.port(8081))
            .addFeature(
                ObserveFeature.builder()
                    // Set name of socket as defined in the WebServer builder.
                    // This will enable the /observe endpoints on port 8081.
                    .sockets(List.of("observe"))
                    .addObserver(healthObserver)
                    .build())
            .build()
            .start();
  }
...

When you start your application you must use port 8081 to access /observe endpoints.

$ curl -X GET http://localhost:8081/observe/health | jq -r .
{
  "status": "UP",
  "checks": []
}
$

Written with Helidon 4.1.6.

February 17, 2025

Helidon SE Helpings: Add Git Information To Info Endpoint

In a previous post you learned how to add information to the /observe/info endpoint. You can also add Git information to the endpoint. For example you can add the Git commit id so you can see check, when the application is running in a production environment, which Git commit for the code was deployed. In order to achieve this you must first generate a properties file with all Git information. The next step is to process this file in your Helidon SE application and add the properties to the /observe/info endpoint.

Define using default configuration

First you need to add the git-commit-id-maven-plugin Maven plugin to your pom.xml file. This plugin can generate a properties file (git.properties) with Git information. In order to generate property names that can be automatically picked up by Helidon you must add the prefix server.features.observe.observers.info.values.git. The Maven plugin will generate a lot of information. You can define regular expressions for property names that should be included in the generated git.properties file. The default location of the generated file is ${project.build.outputDirectory}/git.properties. This means it will be in the root of the classpath of your application.

In the following snippet you can see the configuration of the Maven plugin:

<!-- File: pom.xml -->
...
<plugin>
    <groupId>io.github.git-commit-id</groupId>
    <artifactId>git-commit-id-maven-plugin</artifactId>
    <version>${git-commit-id-maven-plugin.version}</version>
    <executions>
        <execution>
            <id>get-git-info</id>
            <goals>
                <goal>revision</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <!-- Enable generation of file
             ${project.build.outputDirectory}/git.properties
             with Git properties. -->
        <generateGitPropertiesFile>true</generateGitPropertiesFile>
        <!-- Set prefix so Helidon can include the
             generated properties automatically. -->
        <prefix>server.features.observe.observers.info.values.git</prefix>
        <!-- Include all properties that match the following patterns. -->
        <includeOnlyProperties>
            <includeOnlyProperty>.*git.branch$</includeOnlyProperty>
            <includeOnlyProperty>.*git.commit.(time|id)$</includeOnlyProperty>
        </includeOnlyProperties>
    </configuration>
</plugin>
...

When you run the Maven compile phase a new git.properties file is generated. The following shows an example of the contents of this properties file:

#Generated by Git-Commit-Id-Plugin
server.features.observe.observers.info.values.git.branch=main
server.features.observe.observers.info.values.git.commit.id=68eb0e361ff27dea59e34c40719fa84125fc46cb
server.features.observe.observers.info.values.git.commit.time=2025-02-15T11\:26\:14+01\:00

To include this information in the output of the /observer/info endpoint you must first include the following dependency so Helidon enables this endpoint:

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

Next you must add the generated git.properties file as a source of configuration properties using the Config.builder().addSource() method. Then you can use the configuration to create a WebServer instance. The properties prefixed with server.features.observe.observers.info.values will be picked up by Helidon automatically and added to the info endpoint. This includes the properties from git.properties.

In the following code example you can see how to create a WebServer instance using the configuration:

// File: Main.java
...
import io.helidon.webserver.WebServer;
import io.helidon.config.Config;
...
  public static void main(String[] args) {
    ...
    Config config =
        Config.builder()
            // Support environment variables and system properties.
            .metaConfig()
            .sources(
              // Use the application.properties file from the classpath.
              ConfigSources.classpath("application.properties"),
              
              // Use the git.properties file from the classpath.
              // This file is created by the git-commit-id-maven-plugin
              // Maven plugin.
              ConfigSources.classpath("git.properties"))
              
            .build();

    WebServer server =
        WebServer.builder()
            ...
            // Read configuration properties.
            // This will use all properties with the prefix
            // "server.features.observe.observers.info.values."
            // for the /observe/info endpoint.
            .config(config.get("server"))
            .build()
            .start();
  }
...

If you start the application and go to the /observe/info endpoint you should see the following output:

$ curl  -s -X GET --location "http://localhost:8080/observe/info" \
    -H "Accept: application/json" | jq -r .
{
  "git.branch": "main",
  "git.commit.time": "2025-02-15T11:26:14+01:00",
  "app.name": "blog-sample",
  "git.commit.id": "68eb0e361ff27dea59e34c40719fa84125fc46cb",
  "app.version": "0.0.0-SNAPSHOT"
}

Define using code

Instead of using the default configuration and a prefix of server.features.observe.observers.info.values.git you can also define the logic to define to read the git.properties file in your code. Once the properties are available they can be added to a InfoObserver instance.

First you can remove the prefix option in our pom.xml for the plugin configuration and generate all property names and values as we will filter these in code:

<!-- File: pom.xml -->
...
<plugin>
    <groupId>io.github.git-commit-id</groupId>
    <artifactId>git-commit-id-maven-plugin</artifactId>
    <version>${git-commit-id-maven-plugin.version}</version>
    <executions>
        <execution>
            <id>get-git-info</id>
            <goals>
                <goal>revision</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <!-- Enable generation of file
             ${project.build.outputDirectory}/git.properties
             with Git properties. -->
        <generateGitPropertiesFile>true</generateGitPropertiesFile>
    </configuration>
</plugin>
...

Next you must add code to read the git.properties file and add the properties to an InfoObserver instance. In the following code example you can see how this can be done:

// File: Main.java
...
import io.helidon.webserver.observe.health.HealthObserver;
import io.helidon.webserver.observe.info.InfoObserver;
import io.helidon.webserver.WebServer;
...
  public static void main(String[] args) throws IOException {
    ...
    InfoObserver infoObserver =
        InfoObserver.builder()
                    // Add extra key-value pairs for the
                    // /observe/info endpoint based on
                    // git.properties file.
                    .addValues(getGitInfo())
                    // Extra custom properties that will be
                    // added to the /observe/info endpoint.
                    .addValues(Map.of("app.name", "blog-sample",
                                      "app.version", "0.0.0-SNAPSHOT"))
                    .build();

    WebServer server =
        WebServer.builder()
            ...
            // Add the info observer to the server.
            .addFeature(ObserveFeature.create(infoObserver))
            .build()
            .start();
  }

  /**
   * Read {@code git.properties} file and return a map with a subset of the properties.
   *
   * @return Map with {@code git.branch}, {@code git.commit.id} and {@code git.commit.time}.
   * @throws IOException Error reading {@code git.properties} file.
   */
  private static Map<String, String> getGitInfo() throws IOException {
    Properties props = new Properties();
    props.load(Main.class.getResourceAsStream("/git.properties"));
    return Map.of(
        "git.branch", props.getProperty("git.branch"),
        "git.commit.id", props.getProperty("git.commit.id"),
        "git.commit.time", props.getProperty("git.commit.time"));
  }
...

Written with Helidon 4.1.6.

February 11, 2025

Helidon SE Helpings: Adding Information To Info Endpoint

It is possible to add an endpoint to Helidon SE that can show information about the application. You can add custom information to this endpoint. In order to enable the endpoint you need to add the dependency io.helidon.webserver.observe:helidon-webserver-observe-info to your pom.xml file. This will add the endpoint /observe/info to your application. You can add key-value pairs to your configuration or code that will be exposed in the endpoint.

To start using the endpoint you must add the following dependency to your pom.xml file:

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

You can define the information that should be exposed by the endpoint in your configuration file. Any property name and value prefixed with server.features.observe.observers.info.values. will be added to the endpoint:

# File: src/main/resources/application.properties
...
server.features.observe.observers.info.values.version=1.0.2
server.features.observe.observers.info.values.app-name=Helidon-Sample
...

Instead of using fixed values for the properties you can also use values from properties coming from the pom.xml file. You can refer to properties name using @{property}@ or ${property} syntax. You must enable resource filtering in your pom.xml:

...
    <build>
        ...
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>application.properties</include>
                </includes>
            </resource>
        </resources>  
        ...
    </build>
...

So for our previous example you can also use the POM project properties project.version and project.artifactId in the application.properties file. When the file is copied to the target directory by Maven the placeholders will be replaced with the actual values.

# File: src/main/resources/application.properties
...
server.features.observe.observers.info.values.version=@project.version@
server.features.observe.observers.info.values.app-name=@project.artifactId@
...

Next you need to read the configuration file and use it to configure a WebServer instance:

import io.helidon.config.Config;
import io.helidon.webserver.WebServer;
...
  public static void main(String[] args) {
    ...
    Config config = Config.create();

    WebServer server =
        WebServer.builder()
            ...
            .config(config.get("server"))
            .build()
            .start();
  }
...

Instead of defining the properties for the /observe/info endpoint in a properties file you can also programmatically define them in your code. You must create a InfoObserver object and add the properties to the instance using the addValues method. This InfoObserver object can be assigned as ObserverFeature to a WebServer instance. In the following example you can see how to define a version and app-name property with some hardcoded value in the application code.

import io.helidon.webserver.WebServer;
import io.helidon.webserver.observe.ObserveFeature;
import io.helidon.webserver.observe.info.InfoObserver;
import java.util.Map;
...
  public static void main(String[] args) {
    ...
    // Create InfoObserver instance and add properties version and app-name.
    InfoObserver infoObserver =
        InfoObserver.builder()
            .addValues(Map.of("version", "1.0.0", "app-name", "Helidon-Sample"))
            .build();

    WebServer server =
        WebServer.builder()
            ...
            // Add observe feature with info observer.
            .addFeature(ObserveFeature.create(infoObserver))
            .build()
            .start();
  }
...

If you start your Helidon SE application you can see the information in the /observe/info endpoint. In the following example we use a curl command to get the information:

$ curl -s -X GET --location "http://localhost:8080/observe/info" | jq .
{
  "app-name": "Helidon-Sample",
  "version": "1.0.0"
}
$

Written with Helidon SE 4.1.6.

February 8, 2025

Helidon SE Helpings: Return Response Based On Request Accept Header

Suppose you want to return a response based on the Accept header of the request. If the Accept header is application/json you want to return a JSON response and if the Accept header is application/xml you want to return an XML response. You can use the isAccepted(MediaType) of the ServerRequestHeaders class to check if the value of the request header Accept is equal to the specified media type. The method returns true if the media type is defined for the request header Accept and false if not.

In order to convert an object to XML you need to add the dependency com.fasterxml.jackson.dataformat:jackson-dataformat-xml to your pom.xml file:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

In the following example HttpService implementation the request header Accept is used to determine if the response should be in XML or default JSON format:

package mrhaki;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import io.helidon.common.media.type.MediaTypes;
import io.helidon.http.HeaderNames;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;

/** Example service to return data in either XML or JSON depending on the request header Accept. */
class SampleService implements HttpService {
  /** Use for converting an object to XML. */
  private final XmlMapper xmlMapper = new XmlMapper();

  @Override
  public void routing(HttpRules rules) {
    rules.get("/content", this::handle);
  }

  private void handle(ServerRequest req, ServerResponse resp) throws JsonProcessingException {
    // Define record for data structure to send in the response.
    record Message(String content) {}

    // Check if the Accept header is set with the value application/xml.
    if (req.headers().isAccepted(MediaTypes.APPLICATION_XML)) {
      // Return XML response.
      resp.header(HeaderNames.CONTENT_TYPE, MediaTypes.APPLICATION_XML.text())
          .send(xmlMapper.writeValueAsString(new Message("Sample")));
    } else {
      // Return default JSON response.
      resp.send(new Message("Sample"));
    }
  }
}

When you access the endpoint with different values for the request header Accept you get a response in the format you specified:

$ curl -s -X GET --location "http://localhost:8080/sample/content" \
    -H "Accept: application/xml" | xmllint --format -
<?xml version="1.0">
<Message>
  <content>Sample</content>
</Message>
$
$ curl -s -X GET --location "http://localhost:8080/sample/content" \
    -H "Accept: application/json" | jq -r .
{
  "content": "Sample"
}
$

Written with Helidon SE 4.1.6.

Spring Sweets: Use Logging in EnvironmentPostProcessor Implementation

You can write an implementation of the interface EnvironmentPostProcessor to customize the Environment of a Spring Boot application. For example you can read an external configuration file and add its properties to the Environment. If you want to add some logging statement to the class then you need to make sure you pass a DeferredLogFactory to the constructor of the class. From this factory you can use the getLogger method to get a Log instance. This is needed, because the implementation of the interface EnvironmentPostProcessor is created before the logging system is initialized. By using the Log instances created from DeferredLogFactory Spring Boot will make sure the log messages are written when the logging system is initialized.

In the following example you can see how to use the DeferredLogFactory:

package mrhaki.sample;

import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;

/**
 * Sample implementation for {@link EnvironmentPostProcessor}.
 */
@Order
public class SampleEnvironmentPostProcessor implements EnvironmentPostProcessor {

  /**
   * Log instance to log messages after the logging system is initialized.
   */
  private final Log log;

  /**
   * Create instance.
   *
   * @param logFactory Use logFactory to create logger that will output if the logging system is
   *     initialized.
   */
  public SampleEnvironmentPostProcessor(DeferredLogFactory logFactory) {
    log = logFactory.getLog(SampleEnvironmentPostProcessor.class);
  }

  @Override
  public void postProcessEnvironment(
      ConfigurableEnvironment environment, SpringApplication application) {
    if (log.isInfoEnabled()) {
      // This log message will only be written once the logging system is initialized
      // and the log level is INFO or higher.
      log.info("Sample environment post processor started");
    }
  }
}

The support for DeferredLogFactory is added in Spring Boot 2.4.

Written with Spring Boot 3.4.2.

February 7, 2025

Helidon SE Helpings: Configure Memory Health Check

You can configure a memory health check in Helidon SE. A memory health check will return a status of UP if the memory usage is below a certain threshold percentage and DOWN if the memory usage is above the threshold percentage. The default threshold percentage is 98%. To add the memory health check you need to add the dependency io.helidon.health:helidon-health-checks to your pom.xml file. This dependency contains three health checks: disk space usage, memory usage and dead lock detection.

To configure the memory health check you need to set the configuration property server.features.observe.observers.health.helidon.health.memory.thresholdPercent to the threshold percentage. Alternatively you can set the threshold percentage in your application code.

In the following example application you add a memory usage health check. First you add the dependency to the pom.xml file:

<dependency>
  <groupId>io.helidon.health</groupId>
  <artifactId>helidon-health-checks</artifactId>
</dependency>

To configure the memory health check you can add the property server.features.observe.observers.health.helidon.health.memory.thresholdPercent to the application.properties file.

...
# Set threshold percentage for memory usage.
# When the memory usage is above this percentage, the health check will fail.
# Default is 98.0
server.features.observe.observers.health.helidon.health.memory.thresholdPercent=90.0
...

In your application code you need to use the configuration to create the WebServer instance:

import io.helidon.webserver.WebServer;
import io.helidon.config.Config;
...
  public static void main(String[] args) {
    ...
    Config config = Config.create();

    WebServer server =
        WebServer.builder()
            ...
            // Read configuration properties.
            .config(config.get("server"))
            .build()
            .start();
  }
...

Instead of setting the threshold percentage in the configuration file you can also set it in your application code:

import io.helidon.health.checks.MemoryHealthCheck;
import io.helidon.webserver.WebServer;
import io.helidon.webserver.observe.ObserveFeature;
import io.helidon.webserver.observe.health.HealthObserver;
...
  public static void main(String[] args) {
    ...
    // Configure memory usage health check.
    HeapMemoryHealthCheck heapMemoryHealthCheck =
        HeapMemoryHealthCheck.builder().thresholdPercent(90.0).build();

    // Create observe feature with health check.
    HealthObserver healthObserver =
        HealthObserver.builder()
            // Show details in health endpoint.
            .details(true)
            // Disable auto-discovery of health checks.
            .useSystemServices(false)
            // Add heap memory health check.
            .addCheck(heapMemoryHealthCheck)
            .build();

    WebServer server =
        WebServer.builder()
            .port(8080)
            // Add observe feature with health check.
            .addFeature(ObserveFeature.create(healthObserver))
            .build()
            .start();
  }
...

Once you start the application you can access the health endpoint at http://localhost:8080/health:

$ curl --silent -X GET --location "http://localhost:8080/observe/health" | jq -r .
{
  "status": "UP",
  "checks": [
    {
      "name": "heapMemory",
      "status": "UP",
      "data": {
        "free": "39.25 MB",
        "freeBytes": 41160992,
        "max": "50.00 MB",
        "maxBytes": 52428800,
        "percentFree": "78.51%",
        "total": "50.00 MB",
        "totalBytes": 52428800
      }
    }
  ]
}

Written with Helidon SE 4.1.6.

January 29, 2025

Helidon SE Helpings: Configure Disk Space Health Check

In Helidon SE you can enable a health check for the disk space usage. If the disk space usage is above a certain threshold then the health check will fail. To enable the disk space health check you need to add the dependency io.helidon.health:helidon-health-checks to your pom.xml file. The dependency contains three health checks: disk space usage, memory usage and dead lock detection. To configure the disk space health check you need to set the configuration property server.features.observe.observers.health.helidon.health.diskSpace.thresholdPercent to the threshold percentage. Or programmatically set the value in your application code. The default value is 99.999, which means that in real life the health check will not fail. You need to set a lower percentage in order to see the health check fail. For example when you set the value to 95.0 then the health check will fail when the disk space usage is above 95% or less than 5% of the disk space is available. You can also configure the path to check for disk space usage. The default path is the current working directory, but it can be set to another path. You need to set the configuration property server.features.observe.observers.health.helidon.health.diskSpace.path to change the path.

In the following example application you add a disk space usage health check. First you add the dependency to the pom.xml file:

<dependency>
  <groupId>io.helidon.health</groupId>
  <artifactId>helidon-health-checks</artifactId>
</dependency>

Next you configure the disk space health check. You need to enable the details view of the health endpoint. Next you set the threshold percentage and the path to check for disk space usage. If your application uses application.properties then you add the following lines to the configuration file:

...
# Enable detailed information for the health endpoint.
server.features.observe.observers.health.details=true

# Set threshold percentage for disk space usage.
# When the disk space usage is above this percentage, the health check will fail.
# Default is 99.999
server.features.observe.observers.health.helidon.health.diskSpace.thresholdPercent=95.0

# Configure path to check for disk space usage.
# Default path is "."
server.features.observe.observers.health.helidon.health.diskSpace.path=/mnt/data
...

In your application code you need to use the configuration to create the WebServer instance:

import io.helidon.webserver.WebServer;
import io.helidon.config.Config;
...
  public static void main(String[] args) {
    ...
    Config config = Config.create();

    WebServer server =
        WebServer.builder()
            ...
            // Read configuration properties.
            .config(config.get("server"))
            .build()
            .start();
  }
...

The configuration can also be done in application code. In the following example you configure the disk space health check in the application code:

import io.helidon.health.checks.DiskSpaceHealthCheck;
import io.helidon.webserver.WebServer;
import io.helidon.webserver.observe.ObserveFeature;
import io.helidon.webserver.observe.health.HealthObserver;
...
  public static void main(String[] args) {
    ...
    // Configure disk space usage health check.
    DiskSpaceHealthCheck diskSpaceHealthCheck =
        DiskSpaceHealthCheck.builder()
            // Set threshold percentage for disk space usage.
            // When the disk space usage is above this percentage, the health check will fail.
            // Default is 99.999
            .thresholdPercent(95.0)
            // Configure path to check for disk space usage.
            // Default path is "."
            .path("/mnt/data")
            .build();

    // Create observe feature with health check.
    HealthObserver healthObserver =
        HealthObserver.builder()
            // Show details in health endpoint.
            .details(true)
            // Disable auto-discovery of health checks.
            .useSystemServices(false)
            // Add disk space usage health check.
            .addCheck(diskSpaceHealthCheck)
            .build();

    WebServer server =
        WebServer.builder()
            ...
            .port(8080)
            // Add observe feature with health check.
            .addFeature(ObserveFeature.create(healthObserver))
            .build()
            .start();
  }
...

When you access the health endpoint you see the disk space usage health check:

$ curl --silent -X GET --location "http://localhost:8080/observe/health" | jq -r .
{
  "status": "DOWN",
  "checks": [
    {
      "name": "diskSpace",
      "status": "DOWN",
      "data": {
        "free": "18.94 GB",
        "freeBytes": 20333867008,
        "percentFree": "4.07%",
        "total": "465.63 GB",
        "totalBytes": 499963174912
      }
    }
  ]
}

Written with Helidon SE 4.1.6.

January 24, 2025

Helidon SE Helpings: Show Details For Health Endpoint

With Helidon SE you can add a health endpoint to your application by simply adding a dependency. In your pom.xml you have to add the dependency io.helidon.webserver.observe:helidon-webserver-observe-health. This adds a new endpoint /health to your application. When your application is up and running the /health endpoint will return the HTTP status code 204 with an empty body. If your application is not healthy then the HTTP status code 503 is returned. In case of an error an HTTP status code 500 is sent to the client.

If you want to see a response with details then you need to set the configuration property server.features.observe.observers.health.details to the value true. Instead of the HTTP status code 204 the status code 200 is returned when our application is healthy. The response contains a JSON object with a status field with the value UP for a healthy response. The response also contains the field checks with an array of detailed information from health checks that are available in our application.

In the following example application you add a health endpoint with details. First you add the dependency to the pom.xml file:

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

Next you can start the application to try out the health endpoint. Using a HTTP client, for example curl, you can send a GET request to the health endpoint:

$ curl -X GET -i "http://localhost:8080/observe/health"
HTTP/1.1 204 No Content
Date: Fri, 24 Jan 2025 15:55:06 +0100
Connection: keep-alive
Content-Length: 0

You see that the HTTP status code 204 is returned.

In order to see some more details you need to set the configuration property server.features.observe.observers.health.details to the value true. Depending on the configuration file format that is supported you add this property to a configuration file. If you use application.properties simply add the following line:

...
server.features.observe.observers.health.details=true
...

Alternatively you can also set the property value using Java system properties or environment variables.

You need to read in the configuration and use it configure the WebServer instance:

import io.helidon.webserver.WebServer;
import io.helidon.config.Config;
...
  public static void main(String[] args) {
    ...
    Config config = Config.create();

    WebServer server =
        WebServer.builder()
            ...
            .config(config.get("server"))
            .build()
            .start();
  }
...

Now you can send a GET request to the health endpoint again:

$ curl -X GET -i "http://localhost:8080/observe/health"
HTTP/1.1 200 OK
Date: Fri, 24 Jan 2025 15:57:35 +0100
Connection: keep-alive
Content-Length: 27
Content-Type: application/json

{"status":"UP","checks":[]}

The application doesn’t have any extra health checks so the checks array is empty. You can also see the HTTP status code 200 is returned and the response contains a JSON object with the field status with the value UP.

You can also use Java code to add a detailed health check. In the following example we add a feature to our code that creates the WebServer instance:

import io.helidon.webserver.WebServer;
import io.helidon.config.Config;
...
  public static void main(String[] args) {
    ...
    Config config = Config.create();

    WebServer server =
        WebServer.builder()
            ...
            .addFeature(
                ObserveFeature.builder()
                    // Add health observer with details
                    .addObserver(HealthObserver.builder().details(true).build())
                    .build())
            // Allow override of configuration properties
            .config(config.get("server"))
            .build()
            .start();
  }
...

Written with Helidon 4.1.6.

November 3, 2024

Mastering Maven: Disable Logging Of Progress Downloading Artifacts

When Maven needs to download artifacts from a remote repository, it logs the progress of the download. This can lead to a lot of noise in the output. Luckily, we can suppress the logging of the download progress. Since Maven 3.6.1. we can use the command-line option --no-transfer-progress to disable the logging of the download progress. There is also a short version of the option: -ntp.

First look at an example where we do not disable the logging of the download progress. In the output, we see (a lot of) messages showing which artifacts are downloaded and the progress.

$ mvn package
[INFO] Scanning for projects...
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/3.3.5/spring-boot-starter-parent-3.3.5.pom
Downloaded from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/3.3.5/spring-boot-starter-parent-3.3.5.pom (13 kB at 29 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-dependencies/3.3.5/spring-boot-dependencies-3.3.5.pom
Downloaded from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-dependencies/3.3.5/spring-boot-dependencies-3.3.5.pom (100 kB at 1.7 MB/s)
...
[INFO]
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-maven-plugin/3.3.5/spring-boot-maven-plugin-3.3.5.pom
Downloaded from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-maven-plugin/3.3.5/spring-boot-maven-plugin-3.3.5.pom (4.0 kB at 308 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-maven-plugin/3.3.5/spring-boot-maven-plugin-3.3.5.jar
Downloaded from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-maven-plugin/3.3.5/spring-boot-maven-plugin-3.3.5.jar (137 kB at 6.5 MB/s)
...

We remove the downloaded dependencies from our local repository and run the same Maven command again, but now we add the command-line option --no-transfer-progress (or the short version -ntp). We no longer have the downloading progress messages in our output:

$ mvn --no-transfer-progress package
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
...

Instead of adding this option each time we run Maven, we can add it to the file .mvn/maven.config in our project:

--no-transfer-progress

Alternatively, since Maven 3.9.0, we can add the option to the environment variable MAVEN_ARGS.

$ export MAVEN_ARGS=-ntp 
$ mvn package
[INFO] Scanning for projects...
[INFO]
...

Written with Maven 3.9.9.

October 24, 2024

Helidon SE Helpings: Default Configuration Sources During Testing

In a previous blog post we learned about the default input sources that are used by Helidon SE. The list of input sources is different based on which artifacts are on the classpath of our application. When we write tests for code in our application that uses the default configuration created by Config.create() we must take into account that different input sources are used. Also here it is based on the artifacts that are on the classpath. That means that different files with configuration data are loaded, eg. a file application-test.conf when we have the artifact helidon-config-hocon and a file application-test.yml if the artifact helidon-config-yaml is on the classpath.

If we use the artifact helidon-config then the following input sources are searched with the following order of preference:

  1. System environment variables
  2. Java system properties
  3. a file META-INF/microprofile-config-test.properties on the classpath,
  4. a file META-INF/microprofile-config.properties on the classpath.

Notice that there is no input source that looks for a file application.properties on the classpath, but when we run our application that is a valid input source. Also the order of the input sources system environment variables and Java system properties switched. So the input sources for a default configuration created with Config.create() is different during run-time and when we test our application.

In the following test we create a default configuration using Config.create() and get the configuration property app.message that is set using a system environment variable, Java system property and the file META-INF/microprofile-config-test.properties placed in src/test/resources (which will be on the classpath). In order to test with setting a system environment variable in our test code we use the library com.github.stefanbirkner:system-lambda by adding the following dependency:

<dependency>
    <groupId>com.github.stefanbirkner</groupId>
    <artifactId>system-lambda</artifactId>
    <version>1.2.1</version>
    <scope>test</scope>
</dependency>

Our properties file looks like this:

# File: src/test/resources/META-INF/microprofile-config-test.properties
app.message=Hello from classpath:META-INF/microprofile-config-test.properties

Our test class has three test methods to check the value of the configuration property app.message:

// File: src/test/java/mrhaki/helidon/DefaultConfigTest.java
package mrhaki.helidon;

import io.helidon.config.Config;
import org.junit.jupiter.api.Test;

import static com.github.stefanbirkner.systemlambda.SystemLambda.restoreSystemProperties;
import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable;
import static org.assertj.core.api.Assertions.assertThat;

public class DefaultConfigTest {

    @Test
    void defaultConfig() throws Exception {
        // expect
        withEnvironmentVariable("APP_MESSAGE", "Hello from environment variable")
                .execute(() -> {
                    final Config config = Config.create();

                    assertThat(config.get("app.message").asString().asOptional())
                            .hasValue("Hello from environment variable");
                });
    }

    @Test
    void withSystemProperties() throws Exception {
        restoreSystemProperties(() -> {
            System.setProperty("app.message", "Hello from Java system property");
            assertThat(Config.create().get("app.message").asString().asOptional())
                    .hasValue("Hello from Java system property");
        });
    }

    @Test
    void withConfigTestProperties() {
        // given
        final Config config = Config.create();

        // expect
        assertThat(config.get("app.message").asString().asOptional())
                .hasValue("Hello from classpath:META-INF/microprofile-config-test.properties");
    }
}

When we apply the artifact helidon-config-hocon in our pom.xml file then the following input sources are searched with the following order of preference:

  1. System environment variables,
  2. Java system properties,
  3. a file application-test.json,
  4. a file application-test.conf,
  5. classpath:application-test.json,
  6. a file application-test.conf on the classpath,
  7. a file application.json,
  8. a file application.conf,
  9. a file application.json on the classpath,
  10. a file application.conf on the classpath,
  11. a file META-INF/microprofile-config-test.properties on the classpath,
  12. a file META-INF/microprofile-config.properties.` on the classpath

If we would use the artifact helidon-config-yaml then the following input sources are searched with the following order of preference:

  1. System environment variables,
  2. Java system properties,
  3. a file application-test.yml,
  4. a file application-test.yaml,
  5. a file application-test.yml on the classpath,
  6. a file application-test.yaml on the classpath,
  7. a file application.yml,
  8. a file application.yaml,
  9. a file application.yml on the classpath,
  10. a file application.yaml on the classpath,
  11. a file META-INF/microprofile-config-test.properties on the classpath,
  12. a file META-INF/microprofile-config.properties on the classpath.

Written with Helidon SE 4.1.2.