Building High-Performance Time-Series Applications With Java and QuestDB
QuestDB combines fast time-series ingestion with a familiar SQL model. With Eclipse JNoSQL 1.1.18, Java developers can use it through TimeSeriesTemplate and Jakarta Data.
Join the DZone community and get the full member experience.
Join For FreeQuestDB is well-suited for applications necessitating high-performance ingestion and SQL access to time-oriented data. It is especially useful in financial markets, real-time analytics, observability, telemetry, and operational systems where data arrives continuously and must be queried with low latency. Unlike general-purpose databases, QuestDB is purpose-built with time as a core element of both storage and query processes.
QuestDB appeals to enterprise developers by combining a time-series architecture with a familiar SQL interface. This technique reduces the learning curve for teams experienced in relational querying while providing a database optimized for append-heavy, chronological workloads. For systems needing recent-state queries, historical analysis, trend detection, and rapid ingestion, QuestDB delivers a practical solution.
Why QuestDB Matters
QuestDB excels when applications require both high ingestion rates and fast analytical queries on recent and historical data. This is essential for workloads with continuous data streams where rapid business response is critical, such as market prices, trading activity, telemetry, infrastructure metrics, clickstream data, operational events, and real-time business indicators. In these cases, the database must efficiently manage append-heavy data while supporting intuitive SQL queries across time ranges.
As a result, QuestDB is well suited for financial platforms, observability systems, logistics, energy, IoT, and real-time analytics. For example, a trading system can query both the latest prices and historical intervals, an operations platform can analyze recent latency and throughput, and a logistics application can review vehicle or delivery telemetry over time. Its SQL-oriented model is especially valuable for enterprise teams, offering a familiar query language alongside an architecture designed for time-series workloads.
Hands-On: Java With QuestDB
We will build a simple Java application using QuestDB as the time-series database. For development, the easiest way to begin is with Docker:
docker run -d \
--name questdb-instance \
-p 9000:9000 \
questdb/questdb:10.0.1
QuestDB provides its Web Console and HTTP endpoint on port 9000. Once the container is running, you can open http://localhost:9000 in your browser to view the data.
Configuring the Application
Eclipse JNoSQL uses Jakarta APIs such as CDI and JSON-B, along with Eclipse MicroProfile Config. These APIs are available in popular Java runtimes including Helidon, Quarkus, Payara, and Open Liberty.
Add the QuestDB driver dependency:
<dependency>
<groupId>org.eclipse.jnosql.databases</groupId>
<artifactId>jnosql-questdb</artifactId>
<version>${jnosql.version}</version>
</dependency>
Next, configure the Time Series database and QuestDB connection in microprofile-config.properties:
jnosql.timeseries.database=qdb
jnosql.questdb.url=ws::addr=localhost:9000
The qdb value specifies the logical database for the JNoSQL Time Series manager. The QuestDB URL uses the connection format required by the driver.
Modeling Sensor Data
For this example, we will use a simple sensor reading model:
@Entity
public class SensorReading {
@Id
private Instant id;
@Column
private String sensor;
@Column
private double temperature;
@Column
private double humidity;
// constructors, getters, and setters
}
The Instant field records the measurement time, while the other fields describe the sensor reading at that moment.
You can also expose the entity through Jakarta Data:
@Repository
public interface SensorReadingRepository
extends BasicRepository<SensorReading, Instant> {
List<SensorReading> findBySensorOrderByIdDesc(
String sensor,
Limit limit);
}
Using TimeSeriesTemplate
You can now insert a sequence of readings and query both the latest state and recent history:
public class App {
public static void main(String[] args) {
var firstReading = new SensorReading(
Instant.parse("2026-09-20T08:00:00Z"),
"sensor-01",
21.4,
45.0
);
var secondReading = new SensorReading(
Instant.parse("2026-09-20T09:00:00Z"),
"sensor-01",
22.1,
46.5
);
var latestReading = new SensorReading(
Instant.parse("2026-09-20T10:15:00Z"),
"sensor-01",
23.6,
48.2
);
try (SeContainer container =
SeContainerInitializer.newInstance().initialize()) {
TimeSeriesTemplate template =
container.select(TimeSeriesTemplate.class).get();
template.insert(firstReading);
template.insert(secondReading);
template.insert(latestReading);
var currentReading = template
.select(SensorReading.class)
.where("sensor")
.eq("sensor-01")
.orderBy("id")
.desc()
.limit(1)
.singleResult();
System.out.println(
"Current sensor reading: " + currentReading
);
var history = template
.select(SensorReading.class)
.where("sensor")
.eq("sensor-01")
.orderBy("id")
.desc()
.skip(1)
.limit(10)
.result();
System.out.println("Recent sensor history:");
history.forEach(System.out::println);
}
}
}
The first query retrieves the latest reading for sensor-01. The second retrieves a limited history, skipping the most recent observation. This approach better fits typical time-series use cases than retrieving records by identifier.
Using Jakarta Data
You can achieve the same functionality using a Jakarta Data repository:
public class App2 {
public static void main(String[] args) {
var firstReading = new SensorReading(
Instant.parse("2026-09-20T08:00:00Z"),
"sensor-01",
21.4,
45.0
);
var secondReading = new SensorReading(
Instant.parse("2026-09-20T09:00:00Z"),
"sensor-01",
22.1,
46.5
);
var latestReading = new SensorReading(
Instant.parse("2026-09-20T10:15:00Z"),
"sensor-01",
23.6,
48.2
);
try (SeContainer container =
SeContainerInitializer.newInstance().initialize()) {
SensorReadingRepository repository =
container.select(SensorReadingRepository.class).get();
repository.save(firstReading);
repository.save(secondReading);
repository.save(latestReading);
var currentReading = repository
.findBySensorOrderByIdDesc(
"sensor-01",
Limit.of(1)
)
.stream()
.findFirst();
System.out.println(
"Current sensor reading: " + currentReading
);
var history = repository
.findBySensorOrderByIdDesc(
"sensor-01",
Limit.range(2, 10)
);
System.out.println("Recent sensor history:");
history.forEach(System.out::println);
}
}
}
Notably, QuestDB’s time-series capabilities are available through the same Java programming model as other NoSQL time-series drivers. This lets the application focus on temporal queries such as latest state, ordering, and recent history, while the driver handles database-specific details.
Why SQL Matters for QuestDB Adoption
QuestDB stands out for its time-series specialization and SQL-oriented approach. When organizations adopt specialized databases, teams often struggle to learn new query languages and mental models. By providing a familiar SQL interface, QuestDB helps reduce these adoption barriers.
Teams are already familiar with filtering, ordering, grouping, aggregation, and limiting result sets. Keeping these concepts allows programmers to focus on time-based challenges instead of learning a new query system. This is especially valuable when multiple roles need access to the same data.
Financial systems illustrate this well. Market data, exchange rates, trades, and price movements are time-oriented, and many financial professionals already use SQL extensively. QuestDB lets teams leverage existing SQL expertise while gaining from a database optimized for high-frequency ingestion and time-based analysis.
This advantage also applies to observability and operational analytics. Teams often need to query metrics for example request latency, throughput, error rates, or customer activity over specific intervals. Using familiar SQL constructs makes it easier to access application data for analysis.
QuestDB does not function exactly like a traditional relational database. Its architecture and optimizations target time-series workloads. The result is an approachable query experience combined with a storage engine designed for specialized performance.
For enterprises, this combination is powerful: specialized time-series performance without requiring teams to abandon the familiar query model.
Conclusion
QuestDB is a strong fit for applications that need fast ingestion, recent-state queries, historical analysis, and SQL over continuously changing data. With Eclipse JNoSQL 1.1.18, Java developers can use these capabilities through familiar Jakarta APIs, keeping the application model consistent while QuestDB handles the time-series specialization underneath.
Opinions expressed by DZone contributors are their own.
Comments