Spring Boot and InfluxDB with the Java Client
Wire InfluxDB into a Spring Boot app with the official Java client, connection config, writing points, and querying them back. Docker setup included.
InfluxDB is a database built for time series: measurements that arrive constantly, are stamped with a time, and are mostly queried by time range. Sensor readings, application metrics, price ticks. This tutorial connects a Spring Boot application to InfluxDB 2.x using the official Java client, writes points, and queries them back. Everything runs locally in Docker.
Step 1: Running InfluxDB in Docker
The official image can initialise itself on first start when you pass the setup values as environment variables, which saves a trip to the web UI:
docker run -d --name influxdb -p 8086:8086 \
-e DOCKER_INFLUXDB_INIT_MODE=setup \
-e DOCKER_INFLUXDB_INIT_USERNAME=admin \
-e DOCKER_INFLUXDB_INIT_PASSWORD=adminpassword \
-e DOCKER_INFLUXDB_INIT_ORG=my-org \
-e DOCKER_INFLUXDB_INIT_BUCKET=sensors \
-e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-token \
-v influxdb-data:/var/lib/influxdb2 \
influxdb:2This starts a container named influxdb with the service on port 8086, an organisation called my-org, a bucket called sensors, and an API token you will use from the application. The named volume keeps the data across container restarts. The web UI is at http://localhost:8086 if you want to look at what you write.
Three terms matter for the rest of the article. An organisation is a workspace. A bucket is where data lives and carries a retention period, so old points are deleted for you. A token is what a client authenticates with; there are no usernames and passwords on the API.
Step 2: Adding Dependencies to the Spring Boot Project
There is no Spring Boot starter for InfluxDB 2.x. The client is the influxdb-client-java library from InfluxData, and it works fine as a plain bean. Add it to your pom.xml:
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>7.2.0</version>
</dependency>If you are on InfluxDB 1.x, the client is org.influxdb:influxdb-java and the API is different (InfluxQL instead of Flux, databases instead of buckets). The rest of this article is 2.x.
Step 3: Configuring the Connection to InfluxDB
Put the connection values in application.properties. The token comes from the environment, not from the file:
influx.url=http://localhost:8086
influx.token=${INFLUX_TOKEN}
influx.org=my-org
influx.bucket=sensorsBind them to a properties class and expose a single InfluxDBClient bean. The client is thread-safe and holds a connection pool, so one instance for the whole application is the right shape:
@ConfigurationProperties(prefix = "influx")
public record InfluxProperties(String url, String token, String org, String bucket) {}
@Configuration
@EnableConfigurationProperties(InfluxProperties.class)
public class InfluxConfig {
@Bean(destroyMethod = "close")
public InfluxDBClient influxDBClient(InfluxProperties props) {
return InfluxDBClientFactory.create(
props.url(), props.token().toCharArray(), props.org(), props.bucket());
}
}destroyMethod = "close" makes Spring shut the client down with the context, which flushes any buffered writes.
Step 4: Writing Data
There are two write APIs. WriteApiBlocking sends each call immediately and throws on failure; WriteApi batches in the background and reports failures through a listener. Start with the blocking one. It is easier to reason about, and for a few hundred points a second it is fast enough.
@Service
public class TemperatureWriter {
private final InfluxDBClient client;
public TemperatureWriter(InfluxDBClient client) {
this.client = client;
}
public void record(String room, double celsius) {
Point point = Point.measurement("temperature")
.addTag("room", room)
.addField("value", celsius)
.time(Instant.now(), WritePrecision.MS);
client.getWriteApiBlocking().writePoint(point);
}
}A point has a measurement name (temperature), tags, fields and a timestamp. The distinction between tags and fields is the one thing to get right on day one: tags are indexed and fields are not. Put the values you filter or group by in tags (room, sensor_id, region) and the values you measure in fields (value, humidity). Tags are always strings. A field can be a float, integer, string or boolean.
Do not put anything with unbounded values in a tag. A tag like request_id creates a new series for every request, and series cardinality is what makes an InfluxDB instance slow and memory hungry.
If you write in bursts, switch to the batching API and let it coalesce writes:
WriteApi writeApi = client.makeWriteApi(
WriteOptions.builder().batchSize(1_000).flushInterval(1_000).build());
writeApi.writePoint(point);Make that a bean too, so it is closed on shutdown, and subscribe to writeApi.listenEvents(WriteErrorEvent.class, ...) if you want to log failed batches.
Step 5: Querying Data from InfluxDB
InfluxDB 2.x queries are written in Flux. The query below reads the last hour of temperature points for one room and averages them per minute:
public List<FluxRecord> lastHour(String room) {
String flux = """
from(bucket: "sensors")
|> range(start: -1h)
|> filter(fn: (r) => r._measurement == "temperature" and r.room == "%s")
|> aggregateWindow(every: 1m, fn: mean)
""".formatted(room);
List<FluxTable> tables = client.getQueryApi().query(flux);
return tables.stream().flatMap(t -> t.getRecords().stream()).toList();
}Each FluxRecord exposes getTime(), getValue() and getValueByKey("room"). Formatting the room into the query string works for a trusted value; for user input, pass it as a parameter to avoid injection. The query API also accepts a Map<String, Object> of parameters that you reference as params.room inside the Flux script.
If you would rather work with objects, annotate a class and let the client map records onto it:
@Measurement(name = "temperature")
public class TemperatureReading {
@Column(tag = true) String room;
@Column(name = "value") Double value;
@Column(timestamp = true) Instant time;
}
List<TemperatureReading> readings =
client.getQueryApi().query(flux, TemperatureReading.class);Mapping needs one row per point, so add |> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value") to the query when you have more than one field per measurement.
When InfluxDB is the right database, and when your relational database is
A question I get from people reading this: they already have Postgres under their Spring Boot app, so why add a second database? The honest answer is that most applications should not, and the ones that should can tell from three signs.
Write volume with a time on every row. A table that grows by thousands of rows a second and is only ever queried by time range is what InfluxDB is built for. Postgres can do it too, with partitioning or the TimescaleDB extension, but you will be doing that tuning yourself.
Retention as a first-class setting. An InfluxDB bucket with a 30-day retention deletes old data on its own. In a relational database that is a scheduled job you write and a DELETE that competes with your writes.
Aggregation by time window is the main query. aggregateWindow and downsampling tasks are the everyday operations in Flux. The relational equivalent is GROUP BY date_trunc(...), which works but does not have the built-in downsampling pipeline.
If your data is mostly metrics and none of your queries join it to users or orders, InfluxDB is the simpler system to operate. If you need the time series joined against your business tables, or your write rate is a few rows a second, stay in the relational database. Adding a second database is an operational cost that a small write rate does not justify.
There is one more way InfluxDB shows up in a Spring Boot project that has nothing to do with the code above. Spring Boot Actuator can ship its own metrics (JVM memory, HTTP request timings, datasource pool stats) to InfluxDB through Micrometer. Add io.micrometer:micrometer-registry-influx, set management.influx.metrics.export.uri, org, bucket and token, and the application publishes to the same instance with no client code. That is the right tool if all you want is dashboards of the application itself.
Conclusion
In this tutorial, you learned how to connect an InfluxDB database to a Spring Boot application using the official Java client. You saw how to configure a single client bean, write points with tags and fields, and query them back with Flux, either as raw records or mapped onto a class. The tag-versus-field decision and the cardinality rule are the two things worth reading twice; they decide whether the database stays fast as it fills.
Running InfluxDB next to the application is easiest in containers: Docker Compose Tutorial sets up both in one file, and Spring Boot in Docker covers the app side.
AuraLyrics
If you listen to music while you write code, AuraLyrics puts the lyrics over your editor instead of behind another window.
brew install --cask ozcberkay/tap/auralyricsNotes like this one, once a month
Java, AWS and what shipping a side product actually costs.