Integrating Quarkus and OpenTelemetry with a PostgreSQL Database
In this tutorial, we will integrate the Quarkus framework with a PostgreSQL database and compile the application as a native image to observe Quarkus performance when running in native mode.
We will also examine the result of each request and including database queries, using Jaeger distributed tracing.
First, we’ll start with a Maven pom.xml,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.edw</groupId>
<artifactId>quarkus-postgresql</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<compiler-plugin.version>3.14.0</compiler-plugin.version>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>com.redhat.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.27.1.redhat-00003</quarkus.platform.version>
<skipITs>true</skipITs>
<surefire-plugin.version>3.5.2</surefire-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
</properties>
<repositories>
<repository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>redhat</id>
<url>https://maven.repository.redhat.com/ga</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>redhat</id>
<url>https://maven.repository.redhat.com/ga</url>
</pluginRepository>
</pluginRepositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>${maven.compiler.parameters}</parameters>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>
${project.build.directory}/${project.build.finalName}-runner
</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager
</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
</project>
And with the following configuration,
quarkus.application.name=customers-svc
quarkus.http.port=${HTTP_PORT:8080}
quarkus.log.level=INFO
quarkus.log.category."com.edw".level=${LOG_LEVEL:DEBUG}
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %h %-5p [%c{3.}] [%X{traceId},%X{spanId}] (%t) %s%e%n
# opentelemetry
quarkus.otel.exporter.otlp.endpoint=${OTEL_URL:http\://192.168.8.140:4317}
quarkus.otel.sdk.disabled=false
quarkus.datasource.jdbc.telemetry=true
# database
quarkus.datasource.jdbc.url=${JDBC_URL:jdbc\:postgresql\://localhost\:5432/test_db}
quarkus.datasource.jdbc.driver=org.postgresql.Driver
quarkus.datasource.jdbc.max-size=20
quarkus.datasource.jdbc.min-size=2
quarkus.datasource.username=${JDBC_USERNAME:postgres}
quarkus.datasource.password=${JDBC_PASSWORD:postgres}
And the following Java files,
package com.edw.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Entity
@Table(name = "t_customer")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
@Column(name = "customer_id")
private Long customerId;
@Column(name = "customer_name")
private String customerName;
}
package com.edw.service;
import com.edw.model.Customer;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import java.util.List;
@Transactional
@ApplicationScoped
public class CustomerService {
@Inject
EntityManager em;
public List<Customer> findAll() {
return em.createQuery("select c from Customer c order by customerId", Customer.class).getResultList();
}
}
package com.edw.controller;
import com.edw.service.CustomerService;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Path("/api/v1/customers")
public class CustomerController {
private Logger logger = LoggerFactory.getLogger(CustomerController.class);
@Inject
CustomerService customerService;
@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public Response findAll() {
logger.debug("on findAll() method");
return Response
.ok(customerService.findAll())
.build();
}
}
With the following Dockerfile,
FROM quay.io/quarkus/quarkus-distroless-image:2.0 ENV LANGUAGE='en_US:en' ENV TZ='Asia/Jakarta' COPY target/*-runner /application EXPOSE 8080 USER nonroot CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
Build the application as a native image,
$ mvn clean package -Dnative ...... Finished generating 'quarkus-postgresql-1.0-SNAPSHOT-runner' in 1m 24s. [INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildRunner] podman run --env LANG=C --rm --user 115870:115870 --userns=keep-id -v /home/edwin/quarkus-postgresql/target/quarkus-postgresql-1.0-SNAPSHOT-native-image-source-jar:/project:z --entrypoint /bin/bash registry.access.redhat.com/quarkus/mandrel-for-jdk-21-rhel8:23.1 -c objcopy --strip-debug quarkus-postgresql-1.0-SNAPSHOT-runner [INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 97642ms [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 01:41 min [INFO] Finished at: 2025-12-29T11:10:07+07:00 [INFO] ------------------------------------------------------------------------
And containerized it
$ podman build -t quarkus-postgresql:original -f Dockerfile.distroless .
For monitoring, we can use a containerized Jaeger instance to display the distributed tracing of our application,
$ podman run -d --name jaeger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 16686:16686 \
-p 4317:4317 \
jaegertracing/all-in-one:latest
Run the image and make some API calls to the Quarkus application.
We will also be able to see the distributed tracing in the Jaeger UI, including database query spans.
Code for this post can be found on the below repository,
https://github.com/edwin/quarkus-postgresql











