Create a Micronaut Database Application to Collect Metrics and Monitor Them on Oracle Cloud Infrastructure
This guide describes how to use the Graal Development Kit for Micronaut (GDK) to create a Micronaut® database application that collects standard and custom metrics, then publishes and monitors them on Oracle Cloud Infrastructure Monitoring.
Oracle Cloud Infrastructure Monitoring enables you to actively and passively monitor your application and cloud resources using the Metrics and Alarms features.
The application stores book information in a database and provides endpoints to query books. The application collects metrics for the whole application and measures total computation time for a particular endpoint. The application uses Micronaut Micrometer to expose application metric data with Micrometer.
Prerequisites
- 
JDK 17 or higher. See Setting up Your Desktop. 
- 
An Oracle Cloud Infrastructure account. See Setting up Your Cloud Accounts. 
- 
An Oracle Cloud Infrastructure compartment with appropriate permissions to manage Metrics granted to your Oracle Cloud Infrastructure user account. 
- 
A Docker-API compatible container runtime such as Rancher Desktop or Docker installed to run MySQL and to run tests using Testcontainers. 
- 
The GDK CLI. See Setting up Your Desktop. (Optional.) 
Follow the steps below to create the application from scratch. However, you can also download the completed example:
The application ZIP file will be downloaded in your default downloads directory. Unzip it and proceed to the next steps.
A note regarding your development environment
Consider using Visual Studio Code, which provides native support for developing applications with the Graal Development Kit for Micronaut Extension Pack.
Note: If you use IntelliJ IDEA, enable annotation processing.
Windows platform: The GDK guides are compatible with Gradle only. Maven support is coming soon.
1. Create the Application
Create an application using the GDK Launcher.
- 
        Open the GDK Launcher in advanced mode. 
- Create a new project using the following selections.
        - Project Type: Application (Default)
- Project Name: oci-metrics-demo
- Base Package: com.example (Default)
- Clouds: OCI
- Build Tool: Gradle (Groovy) or Maven
- Language: Java (Default)
- Test Framework: JUnit (Default)
- Java Version: 17 (Default)
- Micronaut Version: (Default)
- Cloud Services: Metrics, Database
- Features: GraalVM Native Image, Flyway Database Migration
- Sample Code: No
 
- Click Generate Project, then click Download Zip. The GDK Launcher creates an application with the package com.examplein a directory named oci-metrics-demo. The application ZIP file will be downloaded to your default downloads directory. Unzip it, open it in your code editor, and proceed to the next steps.
Alternatively, use the GDK CLI as follows:
gdk create-app com.example.oci-metrics-demo \
 --clouds=oci \
 --services=metrics,database \
 --features=graalvm,flyway \
 --example-code=false \
 --build=gradle \
 --jdk=17  \
 --lang=javagdk create-app com.example.oci-metrics-demo \
 --clouds=oci \
 --services=metrics,database \
 --features=graalvm,flyway \
 --example-code=false \
 --build=maven \
 --jdk=17  \
 --lang=javaOpen the micronaut-cli.yml file, you can see what features are packaged with the application:
features: [app-name, data, data-jdbc, flyway, gdk-bom, gdk-license, gdk-oci-cloud-app, gdk-oci-database, gdk-oci-metrics, graalvm, http-client, java, java-application, jdbc-hikari, junit, logback, management, maven, maven-enforcer-plugin, micrometer, micrometer-annotation, micrometer-oracle-cloud, micronaut-http-validation, mysql, netty-server, properties, readme, serialization-jackson, shade, static-resources, test-resources, validation]The GDK Launcher creates a multi-module project with two subprojects: oci for Oracle Cloud, and lib for common code and configuration shared across cloud platforms. You develop the application logic in the lib subproject, and keep the Oracle Cloud-specific configurations in the oci subproject.
1.1. Configure Metrics Collection
The project generated by the GDK Launcher has Micrometer as a dependency.
Micrometer provides a simple facade over the instrumentation clients for a number of popular monitoring systems.
To configure Micrometer, the following properties were added to the configuration file, oci/src/main/resources/application.properties, as follows:
micronaut.metrics.enabled=true
micronaut.metrics.binders.files.enabled=true
micronaut.metrics.binders.jdbc.enabled=true
micronaut.metrics.binders.jvm.enabled=true
micronaut.metrics.binders.logback.enabled=true
micronaut.metrics.binders.processor.enabled=true
micronaut.metrics.binders.uptime.enabled=true
micronaut.metrics.binders.web.enabled=trueSeveral groups of metrics are enabled by default: these include system metrics (such as JVM information and uptime), as well as metrics tracking web requests, datasources activity, and others. Overall metrics can be enabled or disabled, and groups can be individually enabled or disabled in configuration. In this case all metrics are enabled. To disable, change to false, for example, per-environment.
To export metrics to Oracle Cloud Infrastructure, configure Oracle Cloud Infrastructure as an export location for Micrometer. This involves enabling the metrics exporter and setting the namespace and destination compartment.
Enable the Oracle Cloud Infrastructure by setting the value of the configuration property micronaut.metrics.export.oraclecloud.enabled to true.
Set the micronaut.metrics.export.oraclecloud.namespace property for your metrics to something meaningful, for example, an application name such as ocimetricsdemo.
The namespace will be used to group the metrics in Oracle Cloud Infrastructure Metrics.
By default, metrics are published in your root compartment (also known as your "tenancy"). To specify the compartment in which metrics should be published, set the value of the micronaut.metrics.export.oraclecloud.compartmentId configuration property to the compartment OCID.
You can set these values in a configuration file, for example application-oraclecloud.properties (see section 6 where this file is used when running the application locally):
micronaut.metrics.export.oraclecloud.enabled=true
micronaut.metrics.export.oraclecloud.namespace=ocimetricsdemo
micronaut.metrics.export.oraclecloud.compartmentId=ocid1.compartment.oc1..your-compartment-code-here # TODO fill in the compartmentId or remove value for rootAlternatively, you can specify them externally as environment variables:
export MICRONAUT_METRICS_EXPORT_ORACLECLOUD_COMPARTMENTID=ocid1.compartment.oc1..aaa....bbbb...
export MICRONAUT_METRICS_EXPORT_ORACLECLOUD_ENABLED=true
export MICRONAUT_METRICS_EXPORT_ORACLECLOUD_NAMESPACE=ocimetricsdemo
set MICRONAUT_METRICS_EXPORT_ORACLECLOUD_COMPARTMENTID=ocid1.compartment.oc1..aaa....bbbb...
set MICRONAUT_METRICS_EXPORT_ORACLECLOUD_ENABLED=true
set MICRONAUT_METRICS_EXPORT_ORACLECLOUD_NAMESPACE=ocimetricsdemo
$ENV:MICRONAUT_METRICS_EXPORT_ORACLECLOUD_COMPARTMENTID="ocid1.compartment.oc1..aaa....bbbb..."
$ENV:MICRONAUT_METRICS_EXPORT_ORACLECLOUD_ENABLED="true"
$ENV:MICRONAUT_METRICS_EXPORT_ORACLECLOUD_NAMESPACE="ocimetricsdemo"
Then, create queries and monitor your metrics in the Oracle Cloud Infrastructure Monitoring Service’s Metrics Explorer UI.
2. Configure Datasources
The GDK Launcher included Flyway for database migrations. It uses the Micronaut integration with Flyway that automates schema changes, significantly simplifies schema management tasks, such as migrating, rolling back, and reproducing in multiple environments. The GDK Launcher enables Flyway in the oci/src/main/resources/application.properties file and configures it to perform migrations on the default datasources.
flyway.datasources.default.enabled=trueIf you specified Flyway as a project feature in the GDK Launcher the build file includes it as a dependency:
oci/build.gradle
implementation("io.micronaut.flyway:micronaut-flyway")
runtimeOnly("org.flywaydb:flyway-mysql")
oci/pom.xml
<dependency>
    <groupId>io.micronaut.flyway</groupId>
    <artifactId>micronaut-flyway</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
    <scope>runtime</scope>
</dependency>
Note: Flyway migrations require full control over schema management. By default, the project does not generate schema automatically in oci/src/main/resources/application.properties. If you manually configure
datasources.default.schema-generate=CREATE_DROPset it toNONEto ensure that only Flyway manages your schema.
Configuring multiple datasources is as simple as enabling Flyway for each one. You can also specify directories that will be used for migrating each datasource. For more information, see Micronaut integration with Flyway.
Flyway migration is automatically triggered before your application starts. Flyway reads migration file(s) in the lib/src/main/resources/db/migration/ directory. The migration file with the database schema, lib/src/main/resources/db/migration/V1__schema.sql, was also created for you by the GDK Launcher.
DROP TABLE IF EXISTS book;
CREATE TABLE book (
    id   BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,
   name  VARCHAR(255) NOT NULL UNIQUE,
   isbn  CHAR(13) NOT NULL UNIQUE
);
INSERT INTO book (isbn, name)
VALUES ("9781491950357", "Building Microservices"),
       ("9781680502398", "Release It!"),
       ("9780321601919", "Continuous Delivery"),
       ("9781617294549", "Microservices Patterns");During application startup, Flyway runs the commands in the SQL file and creates the schema needed for the application.
Using the SQL statements in V1__schema.sql, Flyway creates a table with the name book and populates it with four records.
2.1. Domain Entity
The GDK Launcher created a Book domain class that uses Micronaut Data JDBC in a file named lib/src/main/java/com/example/Book.java, as follows:
package com.example;
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
import static io.micronaut.data.annotation.GeneratedValue.Type.AUTO;
import io.micronaut.serde.annotation.Serdeable;
import jakarta.validation.constraints.Size;
@Serdeable
@MappedEntity (1)
public class Book {
    @Id
    @GeneratedValue(AUTO)
    private Long id;
    private String name;
    @Size(min=13, max=13)
    private String isbn;
    public Book(String isbn, String name) {
        this.isbn = isbn;
        this.name = name;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getIsbn() {
        return isbn;
    }
    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
}1 The annotation @MappedEntity maps the class to the table defined in the schema.
2.2. Repository Interface
A repository interface defines the operations to access the database. Micronaut Data implements the interface at compilation time.
The GDK Launcher created a BookRepository interface in a file named lib/src/main/java/com/example/BookRepository.java:
package com.example;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.repository.CrudRepository;
import jakarta.validation.constraints.NotBlank;
import java.util.Optional;
import static io.micronaut.data.model.query.builder.sql.Dialect.MYSQL;
@JdbcRepository(dialect = MYSQL) (1)
public interface BookRepository extends CrudRepository<Book, Long> { (2)
    @NonNull
    Optional<Book> findByIsbn(@NotBlank String isbn);
}1 A database dialect is specified with the @JdbcRepository annotation.
2 The Book is the root entity, and the primary key type is Long.
2.3. Controller Class
The GDK Launcher created a controller to access Book instances (and to trigger the JDBC metric data) in a file named lib/src/main/java/com/example/BookController.java with the following contents:
package com.example;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.annotation.Counted;
import java.util.Optional;
@Controller("/books") (1)
@ExecuteOn(TaskExecutors.IO)
class BookController {
    private final BookRepository bookRepository;
    BookController(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }
    @Get (2)
    @Timed("books.index") (3)
    Iterable<Book> index() {
        return bookRepository.findAll();
    }
    @Get("/{isbn}")
    @Counted("books.find") (4)
    Optional<Book> findBook(String isbn) {
        return bookRepository.findByIsbn(isbn);
    }
}1 The class is defined as a controller with the @Controller annotation mapped to the path /books.
2 The controller maps a GET request to /books, which returns a list of Book.
3 Use a Micrometer @Timed annotation, with the value “books.index”, to create a timer metric.
4 Use a Micrometer @Counted annotation, with the value “books.find”, to create a counter metric.
3. Create Tests
Note: You require a running Docker container to run the tests.
- 
For tests to run correctly, the GDK Launcher created a test configuration file named oci/src/test/resources/application-test.properties with the following contents: micronaut.metrics.export.oraclecloud.enabled=false micronaut.metrics.enabled=true flyway.datasources.default.enabled=true datasources.default.dialect=MYSQL datasources.default.driverClassName=com.mysql.cj.jdbc.Driver datasources.default.db-type=mysql customMetrics.initialDelay=10hThe configuration enables the metrics and specifies a MySQL datasource. Since it does not specify a URL for the source, Micronaut Test Resources automatically creates a test container for the database. The configuration also enables Flyway to create the bookstables the same way as in a production environment.
- 
The GDK Launcher created a test class, named BookControllerMetricsTest, to verify metrics functionality in a file named oci/src/test/java/com/example/BookControllerMetricsTest.java with the following contents:package com.example; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Timer; import io.micronaut.core.type.Argument; import io.micronaut.http.HttpRequest; import io.micronaut.http.client.HttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.logging.LoggingSystem; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.concurrent.TimeUnit; import static io.micronaut.logging.LogLevel.ALL; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @MicronautTest class BookControllerMetricsTest { @Inject MeterRegistry meterRegistry; @Inject LoggingSystem loggingSystem; @Inject @Client("/") HttpClient httpClient; @Test void testExpectedMeters() { Set<String> names = meterRegistry.getMeters().stream() .map(meter -> meter.getId().getName()) .collect(Collectors.toSet()); // check that a subset of expected meters exist assertTrue(names.contains("jvm.memory.max")); assertTrue(names.contains("process.uptime")); assertTrue(names.contains("system.cpu.usage")); assertTrue(names.contains("logback.events")); assertTrue(names.contains("hikaricp.connections.max")); // these will be lazily created assertFalse(names.contains("http.client.requests")); assertFalse(names.contains("http.server.requests")); } @Test void testHttp() { Timer timer = meterRegistry.timer("http.server.requests", Tags.of( "exception", "none", "method", "GET", "status", "200", "uri", "/books")); assertEquals(0, timer.count()); Timer bookIndexTimer = meterRegistry.timer("books.index", Tags.of("exception", "none")); assertEquals(0, bookIndexTimer.count()); httpClient.toBlocking().retrieve( HttpRequest.GET("/books"), Argument.listOf(Book.class)); assertEquals(1, timer.count()); assertEquals(1, bookIndexTimer.count()); assertTrue(0.0 < bookIndexTimer.totalTime(TimeUnit.MILLISECONDS)); assertTrue(0.0 < bookIndexTimer.max(TimeUnit.MILLISECONDS)); Counter bookFindCounter = meterRegistry.counter("books.find", Tags.of("result", "success", "exception", "none")); assertEquals(0, bookFindCounter.count()); httpClient.toBlocking().retrieve( HttpRequest.GET("/books/9781491950357"), Argument.of(Book.class)); assertEquals(1, bookFindCounter.count()); } @Test void testLogback() { Counter counter = meterRegistry.counter("logback.events", Tags.of("level", "info")); double initial = counter.count(); Logger logger = LoggerFactory.getLogger("testing.testing"); loggingSystem.setLogLevel("testing.testing", ALL); logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); assertEquals(initial + 1, counter.count(), 0.000001); } @Test void testMetricsEndpoint() { Map<String, Object> response = httpClient.toBlocking().retrieve( HttpRequest.GET("/metrics"), Argument.mapOf(String.class, Object.class)); assertTrue(response.containsKey("names")); assertTrue(response.get("names") instanceof List); List<String> names = (List<String>) response.get("names"); // check that a subset of expected meters exist assertTrue(names.contains("jvm.memory.max")); assertTrue(names.contains("process.uptime")); assertTrue(names.contains("system.cpu.usage")); assertTrue(names.contains("logback.events")); assertTrue(names.contains("hikaricp.connections.max")); } @Test void testOneMetricEndpoint() { Map<String, Object> response = httpClient.toBlocking().retrieve( HttpRequest.GET("/metrics/jvm.memory.used"), Argument.mapOf(String.class, Object.class)); String name = (String) response.get("name"); assertEquals("jvm.memory.used", name); List<Map<String, Object>> measurements = (List<Map<String, Object>>) response.get("measurements"); assertEquals(1, measurements.size()); double value = (double) measurements.get(0).get("value"); assertTrue(value > 0); } }The tests verify that certain metrics are present, including the ones that you enabled in the application configuration file, and the ones that you collected thanks to the @Counterand@Timerannotations.Note that, since the @MicronautTestannotation is used, Micronaut initializes the application context and the embedded server with the endpoints you created earlier. (For more information, see the Micronaut Test guide.)
4. Create Custom Metrics
- 
The GDK Launcher created a service that retrieves information from the database and publishes custom metrics based on it. The custom metrics provide the number of books about microservices. The service is in a file named lib/src/main/java/com/example/MicroserviceBooksNumberService.java with the following contents: package com.example; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import io.micronaut.scheduling.annotation.Scheduled; import jakarta.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.StreamSupport; @Singleton public class MicroserviceBooksNumberService { private final Logger log = LoggerFactory.getLogger(getClass().getName()); private final BookRepository bookRepository; private final Counter checks; private final Timer time; private final AtomicInteger microserviceBooksNumber = new AtomicInteger(0); private static final String SEARCH_KEY = "microservice"; MicroserviceBooksNumberService(BookRepository bookRepository, MeterRegistry meterRegistry) { (1) this.bookRepository = bookRepository; checks = meterRegistry.counter("microserviceBooksNumber.checks"); time = meterRegistry.timer("microserviceBooksNumber.time"); meterRegistry.gauge("microserviceBooksNumber.latest", microserviceBooksNumber); } @Scheduled(fixedRate = "${customMetrics.updateFrequency:1h}", initialDelay = "${customMetrics.initialDelay:0s}") (2) public void updateNumber() { time.record(() -> { try { Iterable<Book> allBooks = bookRepository.findAll(); long booksNumber = StreamSupport.stream(allBooks.spliterator(), false) .filter(b -> b.getName().toLowerCase().contains(SEARCH_KEY)) .count(); checks.increment(); microserviceBooksNumber.set((int) booksNumber); } catch (Exception e) { log.error("Problem setting the number of microservice books", e); } }); } }1 The code registers custom meters, queries the database, counts books containing microservicein the name and updates themicroserviceBooksNumber.latestmeter with the value.microserviceBooksNumber.checksstores the number of updates performed, andmicroserviceBooksNumber.timestores the total time spent on the updates for the metric.2 The metric update is scheduled. The customMetrics.updateFrequencyparameter corresponds to the update rate and has the default value of one hour. ThecustomMetrics.initialDelayparameter corresponds to a delay after application startup before metrics calculation and has a default value of zero seconds.
- 
The GDK Launcher created MicroserviceBooksNumberTestto test the custom metrics in a file named oci/src/test/java/com/example/MicroserviceBooksNumberTest.java with the following contents:package com.example; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @MicronautTest class MicroserviceBooksNumberTest { @Inject MeterRegistry meterRegistry; @Inject MicroserviceBooksNumberService service; @Test void testMicroserviceBooksNumberUpdates() { Counter counter = meterRegistry.counter("microserviceBooksNumber.checks"); Timer timer = meterRegistry.timer("microserviceBooksNumber.time"); Gauge gauge = meterRegistry.get("microserviceBooksNumber.latest").gauge(); assertEquals(0.0, counter.count()); assertEquals(0.0, timer.totalTime(MILLISECONDS)); assertEquals(0.0, gauge.value()); int checks = 3; for (int i = 0; i < checks; i++) { service.updateNumber(); } assertEquals((double) checks, counter.count()); assertTrue(timer.totalTime(MILLISECONDS) > 0); assertEquals(2.0, gauge.value()); } }1 The test calls the service three times and verifies that the metrics are collected correctly. Because the Flyway schema added two books with titles containing the word "microservices", the value of microserviceBooksNumber.latestis2.0.
- 
To make sure that the test works as expected and only three updates of the metric are performed, change the test configuration (in the file oci/src/test/resources/application-test.properties) so that it does not perform scheduled updates for the custom metric. To achieve this, change the customMetrics.initialDelayparameter to a large value, such as10hours. For example:customMetrics.initialDelay=10h
5. Run the Tests
Run the tests:
Then open the file oci/build/reports/tests/test/index.html in a browser to view the results.
The tests could be run locally without any cloud credentials, as metrics are not exported to the cloud, and a database is automatically provided by Micronaut test containers.
6. Configure the Application
The GDK Launcher configured an initial datasource in oci/src/main/resources/application.properties with the following properties:
datasources.default.driver-class-name=com.mysql.cj.jdbc.Driver
datasources.default.db-type=mysql
datasources.default.dialect=MYSQLYou can set values for the missing datasources.default.url, datasources.default.username, and datasources.default.password properties with these environment variables:
export DATASOURCES_DEFAULT_PASSWORD=<Password>
export DATASOURCES_DEFAULT_URL=jdbc:mysql://<URL>:3306/gdk
export DATASOURCES_DEFAULT_USERNAME=<Username>
set DATASOURCES_DEFAULT_PASSWORD=<Password>
set DATASOURCES_DEFAULT_URL=jdbc:mysql://<URL>:3306/gdk
set DATASOURCES_DEFAULT_USERNAME=<Username>
$ENV:DATASOURCES_DEFAULT_PASSWORD="<Password>"
$ENV:DATASOURCES_DEFAULT_URL="jdbc:mysql://<URL>:3306/gdk"
$ENV:DATASOURCES_DEFAULT_USERNAME="<Username>"
This approach requires an existing database, or requires you to manually start a server in a Docker container. Instead, to simplify things, do not set the environment variables: if you do not specify a datasource URL, Micronaut Test Resources automatically starts a MySQL server in a Docker container when running the application locally or running tests.
6.1. Configure Oracle Cloud Infrastructure Authentication
If you haven’t created an API-key and configuration file, run the following command:
oci setup bootstrapThe command will open your browser for authentication. When asked, enter your region (for example, ca-toronto-1 for Toronto, Canada) and a name for the profile (for example, oci_metrics_demo_profile).
Note: For more information, see Oracle Cloud Infrastructure SDK Authentication Methods and Setting up the Configuration File.
Use the credentials in the configuration file oci/src/main/resources/application.properties, as follows:
oci.config.profile=oci_metrics_demo_profileProvide the profile name. If you didn’t create a custom profile name, use DEFAULT. Use the oci.config.path property if you need to specify the path to the configuration file.
Note: If you deploy the application to Oracle Cloud Infrastructure, set the property
oci.config.instance-principal.enabled: truefor instance principal authentication. For more details on configuring Oracle Cloud Infrastructure authentication with Micronaut, see Micronaut Oracle Cloud documentation.
7. Run the Application
Before starting the application, set values for Oracle Cloud Infrastructure metrics configuration properties.
You can specify them externally as environment variables:
export MICRONAUT_METRICS_EXPORT_ORACLECLOUD_COMPARTMENTID=ocid1.compartment.oc1..aaa....bbbb...
export MICRONAUT_METRICS_EXPORT_ORACLECLOUD_ENABLED=true
export MICRONAUT_METRICS_EXPORT_ORACLECLOUD_NAMESPACE=ocimetricsdemo
set MICRONAUT_METRICS_EXPORT_ORACLECLOUD_COMPARTMENTID=ocid1.compartment.oc1..aaa....bbbb...
set MICRONAUT_METRICS_EXPORT_ORACLECLOUD_ENABLED=true
set MICRONAUT_METRICS_EXPORT_ORACLECLOUD_NAMESPACE=ocimetricsdemo
$ENV:MICRONAUT_METRICS_EXPORT_ORACLECLOUD_COMPARTMENTID="ocid1.compartment.oc1..aaa....bbbb..."
$ENV:MICRONAUT_METRICS_EXPORT_ORACLECLOUD_ENABLED="true"
$ENV:MICRONAUT_METRICS_EXPORT_ORACLECLOUD_NAMESPACE="ocimetricsdemo"
or you can set them in a configuration file. Since the application will be running locally and not deployed to Oracle Cloud Infrastructure, create a new properties file, oci/src/main/resources/application-dev.properties with the following contents (with your namespace and compartment OCID replaced):
micronaut.metrics.export.oraclecloud.enabled=true
micronaut.metrics.export.oraclecloud.namespace=ocimetricsdemo
micronaut.metrics.export.oraclecloud.compartmentId=ocid1.compartment.oc1..aaa....bbbb..../gradlew :oci:runIf using a properties file, run
MICRONAUT_ENVIRONMENTS=dev ./gradlew :oci:runOr if you use Windows:
cmd /C "set MICRONAUT_ENVIRONMENTS=dev && gradlew :oci:run"Alternatively, to cause the custom metric update to occur more frequently (to see the effects on metrics), start the application with a configuration override to update every five seconds, as follows:
./gradlew :oci:run --args="-customMetrics.updateFrequency=5s"If using a properties file, run
MICRONAUT_ENVIRONMENTS=dev ./gradlew :oci:run --args="-customMetrics.updateFrequency=5s"Or if you use Windows:
cmd /C "set MICRONAUT_ENVIRONMENTS=dev && gradlew :oci:run --args="-customMetrics.updateFrequency=5s""./mvnw install -pl lib -am
./mvnw mn:run -pl ociIf using a properties file, run
./mvnw install -pl lib -am
MICRONAUT_ENVIRONMENTS=dev ./mvnw mn:run -pl ociOr if you use Windows:
mvnw install -pl lib -am
cmd /C "set MICRONAUT_ENVIRONMENTS=dev && mvnw mn:run -pl oci"Alternatively, to cause the custom metric update to occur more frequently (to see the effects on metrics), start the application with a configuration override to update every five seconds, as follows:
./mvnw install -pl lib -am
./mvnw mn:run -pl oci -Dmn.appArgs="-customMetrics.updateFrequency=5s"If using a properties file, run
./mvnw install -pl lib -am
MICRONAUT_ENVIRONMENTS=dev ./mvnw mn:run -pl oci -Dmn.appArgs="-customMetrics.updateFrequency=5s"Or if you use Windows:
mvnw install -pl lib -am
cmd /C "set MICRONAUT_ENVIRONMENTS=dev && mvnw mn:run -pl oci -Dmn.appArgs="-customMetrics.updateFrequency=5s""Note: If your application fails with the message “Private key must be in PEM format”, try creating and adding the SSH key without a passphrase to your Oracle Cloud Infrastructure profile. Both the private key and public key must be in PEM format (not SSH-RSA format). If the issue persists, run the following commands to convert your key to the expected format.
Linux and macOS:
KEY_FOLDER=~/.oci/sessions/oci_metrics_demo_profile cp $KEY_FOLDER/oci_api_key.pem $KEY_FOLDER/oci_api_key_enc.pem openssl pkcs8 -in $KEY_FOLDER/oci_api_key_enc.pem -out $KEY_FOLDER/oci_api_key.pemWindows:
set KEY_FOLDER=/Users/[USERNAME]/.oci/sessions/oci_metrics_demo_profile copy %KEY_FOLDER%/oci_api_key.pem %KEY_FOLDER%/oci_api_key_enc.pem openssl pkcs8 -in %KEY_FOLDER%/oci_api_key_enc.pem -out %KEY_FOLDER%/oci_api_key.pemNote that
opensslis not installed by default on Windows, and requires you to install it.
Send a few test requests with curl, as follows:
- 
Get all the books: curl localhost:8080/books[{"id": 1, "name": "Building Microservices", "isbn": "9781491950357"}, {"id": 2, "name": "Release It!", "isbn": "9781680502398"}, {"id": 3, "name": "Continuous Delivery", "isbn": "9780321601919"}]
- 
Get a book by its ISBN: curl localhost:8080/books/9781680502398{"id": 2, "name": "Release It!", "isbn": "9781680502398"}
- 
Get a list of all the available metrics: curl localhost:8080/metrics{"names": [ "books.find", "books.index", ..., "microserviceBooksNumber.latest", "microserviceBooksNumber.time", ..., "http.server.requests", "process.uptime", ... ]}
- 
Get the value of a particular metric: curl localhost:8080/metrics/http.server.requests{"name": "http.server.requests", "measurements": [ {"statistic": "COUNT", "value": 3.0}, {"statistic": "TOTAL_TIME", "value": 0.6045995000000001}, {"statistic": "MAX", "value": 0.0343463} ], ... }
- 
Get the value of the metric that you created on the booksendpoint:curl localhost:8080/metrics/books.index{"name": "books.index", "measurements": [ {"statistic": "COUNT", "value": 1.0}, {"statistic": "TOTAL_TIME", "value": 0.3218636}, {"statistic":"MAX","value":0.0} ], ... }
- 
Get the value of the custom metric calculating the number of books with microservicein their title:curl localhost:8080/metrics/microserviceBooksNumber.latest{"name": "microserviceBooksNumber.latest", "measurements": [{"statistic": "VALUE", "value" :2.0}] }
Note: When you stop the application, you may see several error messages (such as "failed to post metrics to oracle cloud infrastructure monitoring" and "Client 'oci': Cannot send HTTPS request. SSL is disabled."). These can be safely ignored.
8. Monitor Metrics in Oracle Cloud Infrastructure Metrics Explorer
Use the Oracle Cloud Infrastructure Monitoring Service’s Metrics Explorer UI to view the collected metrics.
- 
In the Oracle Cloud Console, open the navigation menu, click Observability & Management. Under Monitoring, click Metrics Explorer:  
- 
In the query editor, create a query that you want to monitor. Specify the compartment (#1), namespace (#2), and the name of the metric (#3):  
- 
Click Update Chart (#4) and the values of your selected metric are displayed in the Metrics Explorer:  
9. Generate a Native Executable Using GraalVM
The GDK supports compiling Java applications ahead-of-time into native executables using GraalVM Native Image. You can use the Gradle plugin for GraalVM Native Image building/Maven plugin for GraalVM Native Image building. Packaged as a native executable, it significantly reduces application startup time and memory footprint.
Prerequisites: Make sure you have installed a GraalVM JDK. The easiest way to get started is with SDKMAN!. For other installation options, visit the Downloads section.
To generate a native executable, use the following command:
./gradlew :oci:nativeCompileThe native executable oci-metrics-demo is created in the build/native/nativeCompile/ directory
./mvnw install -pl lib -am
./mvnw clean package -pl oci -Dpackaging=native-imageThe native executable oci-metrics-demo is created in the target/ directory
You can customize the name of the resulting binary by updating the Maven/Gradle plugin for GraalVM Native Image configuration.
9.1. Run the Native Executable
Set the environment variables:
export DATASOURCES_DEFAULT_PASSWORD=User123User!
export DATASOURCES_DEFAULT_URL=jdbc:mysql://localhost:3306/gdk
export DATASOURCES_DEFAULT_USERNAME=guide_user
set DATASOURCES_DEFAULT_PASSWORD=User123User!
set DATASOURCES_DEFAULT_URL=jdbc:mysql://localhost:3306/gdk
set DATASOURCES_DEFAULT_USERNAME=guide_user
$ENV:DATASOURCES_DEFAULT_PASSWORD="User123User!"
$ENV:DATASOURCES_DEFAULT_URL="jdbc:mysql://localhost:3306/gdk"
$ENV:DATASOURCES_DEFAULT_USERNAME="guide_user"
To start the native executable, run a Docker container with a MySQL database:
docker run -it --rm \
    --name "mysql.8" \
    -p 3306:3306 \
    -e MYSQL_DATABASE=gdk \
    -e MYSQL_USER=guide_user \
    -e MYSQL_PASSWORD=User123User! \
    -e MYSQL_ALLOW_EMPTY_PASSWORD=true \
    mysql:8Run the native executable with the following command:
./oci/build/native/nativeCompile/oci-metrics-demo-oci./oci/target/oci-metrics-demo-ociNote: With Gradle you can run
./gradlew :oci:nativeRunto start the native executable in development mode, which uses Micronaut Test Resources and therefore doesn’t require you to start a MySQL server.
Run the same curl commands from above to confirm that the application works the same way as before, but with faster startup and response times.
Then return to the Oracle Cloud Console to review the metrics in the Metrics Explorer.
To stop your container run the following command:
docker stop mysql.8Summary
This guide demonstrated how to create a Micronaut application that collects standard and custom metrics, then publishes and monitors them on Oracle Cloud Infrastructure Monitoring. The application uses Micronaut Micrometer to expose application metric data with Micrometer.
