rgoussu@goussu: ~/library/java/storage
~/library/java/storage cat time-series.md

Time-series stores from Java

# InfluxDB and QuestDB clients, TimescaleDB over plain JDBC, and Micrometer as the JVM metrics facade feeding Prometheus and friends.

Conceptsaved 2026-08-09updated 2026-08-11 #java#storage#time-series#metrics#observability

Overview

Time-series access from Java splits into two very different situations. Some stores are their own world with their own client (InfluxDB and its line protocol); some are PostgreSQL in disguise (TimescaleDB, QuestDB's PG-wire endpoint), so the entire JDBC/JPA stack from the relational note applies unchanged. And the most common "time series from Java" case is not a database client at all: it is Micrometer, the metrics facade every framework integrates, with Prometheus scraping the numbers out rather than the app writing them in.

Key points

  • InfluxDB: influxdb-client-java writes points via the line protocol (measurement,tag=v field=1 timestamp), batching built into WriteApi; queries in Flux (2.x) or SQL (3.x). No meaningful ORM layer — you work in points and tables of records.
  • TimescaleDB is just PostgreSQL: standard JDBC driver, HikariCP, JPA/jOOQ/Spring Data — all of it works untouched. Hypertables are invisible to the driver; partitioning happens behind an ordinary table name. Time-series-specific SQL (time_bucket, continuous aggregates) is called like any other SQL function.
  • Prometheus is metrics, not storage you write to: the app exposes /metrics (via prometheus/client_java or, in practice, Micrometer's Prometheus registry) and the server scrapes it; you almost never push (the Pushgateway is for batch jobs).
  • Micrometer is the JVM metrics facade — "SLF4J for metrics": one API (Counter, Timer, Gauge, DistributionSummary), pluggable registries for Prometheus, InfluxDB, Datadog, OTLP and more.
  • Framework integrations: Spring Boot Actuator auto-configures Micrometer and exposes /actuator/prometheus; Quarkus via quarkus-micrometer-registry-prometheus; Micronaut via micronaut-micrometer with per-registry modules. All three instrument HTTP, JVM, and pools out of the box.
  • QuestDB in one line: fastest path in is its Java ILP client (questdb Sender), and it also speaks PG-wire so JDBC works for queries.
  • Client-side concerns are batching and cardinality: write in batches (ILP clients and Influx WriteApi buffer for you; with JDBC use addBatch), and keep tag/label cardinality bounded — unbounded label values (user IDs!) blow up every TSDB and Prometheus alike.

Details

Access routes per store

Store Wire Java layer Framework hook
InfluxDB HTTP line protocol influxdb-client-java (WriteApi, batching) Micrometer Influx registry
TimescaleDB PostgreSQL wire plain JDBC/JPA/jOOQ — the full relational stack Spring Data JPA etc., unchanged
QuestDB ILP (TCP/HTTP) + PG-wire questdb Java Sender; JDBC for queries
Prometheus scrape over HTTP client_java / Micrometer Prometheus registry Boot Actuator, Quarkus/Micronaut Micrometer

Retention and batching from the client side

  • Retention is a server-side policy (Influx retention policies, Timescale drop_chunks/retention policies, Prometheus --storage.tsdb.retention.time) — but the client design must assume old data disappears: no reads that page back forever.
  • Batch writes amortize per-request cost; the failure mode is losing the in-memory buffer on crash. Decide per pipeline whether that loss is acceptable (metrics: yes; billing events: use a durable queue instead of a TSDB write path).

Examples

// Micrometer: instrument once, export anywhere (Prometheus registry shown)
MeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
Timer timer = Timer.builder("orders.process.latency")
    .tag("region", region)          // bounded cardinality only
    .register(registry);
timer.record(() -> processOrder(order));
// InfluxDB line-protocol write with batching handled by the client
try (InfluxDBClient client = InfluxDBClientFactory.create(url, token, org, bucket);
     WriteApi writeApi = client.makeWriteApi()) {
    writeApi.writeRecord(WritePrecision.NS,
        "cpu,host=web01 usage=0.64 1723200000000000000");
}

Related