Get last measurement not working as expected

I am trying to retrieve the last measurement using com.influxdb:influxdb-client-java:7.3.0.

Here’s the query:

QueryApi queryApi = this.influxDBClient.getQueryApi();
String flux = """
		from(bucket:"energy_meter")
			|> range(start: -2d, stop: now())
			|> filter(fn: (r) => r._measurement == "snapshot")
			|> last()
		""";
List<EnergySnapshotMeasurement> result = queryApi.query(flux, EnergySnapshotMeasurement.class);
return (result.size() == 1) ? result.getFirst() : null;

EnergySnapshotMeasurement is as follows:

@Measurement(name = "snapshot")
public static class EnergySnapshotMeasurement {

	@Column(name = "time", timestamp = true)
	private Instant time;

	@Column(name = "active_power")
	private int activePower;

	@Column(name = "in_t1")
	private Float inT1;

	@Column(name = "in_t2")
	private Float inT2;

	@Column(name = "out_t1")
	private Float outT1;

	@Column(name = "out_t2")
	private Float outT2;
}

Rather than a single result, I am getting a list of 5 entries where all fields are null. I understand my query is wrong and it’s retrieving one row per field rather than the last “entry” in the bucket. I don’t know how to adapt my query so that I can get back the last entry.

For the record, I am ingesting data as follows:

this.writeApi.writeMeasurement(WritePrecision.S, measurement);

Where measurement is an instance of `EnergySnapshotMeasurement.

@snicoll In the Flux data model, data gets returned in multiple tables or groups. By default, InfluxDB groups data by the measurement, tag set, and field key. Aggregate and selector functions (in your case, last()) operate on each table. So this query will return the last record value for each unique measurement, tag set, and field key combination.

If you’re just trying the get the last recorded value, you need to ungroup (group all the data into a single table), ensure the data is sorted by _time, and then run last():

from(bucket:"energy_meter")
    |> range(start: -2d, stop: now())
    |> filter(fn: (r) => r._measurement == "snapshot")
    |> group()
    |> sort(columns: ["_time"])
    |> last()

Thanks for the reply. I’ve tried to use groupbut the query above fails with:

com.influxdb.exceptions.BadRequestException: HTTP status code: 400; Message: runtime error @5:7-5:31: sort: schema collision: cannot group integer and float types together

	at com.influxdb.internal.AbstractRestClient.responseToError(AbstractRestClient.java:113)
	at com.influxdb.internal.AbstractQueryApi$1.onResponse(AbstractQueryApi.java:228)
	at com.influxdb.internal.AbstractQueryApi.query(AbstractQueryApi.java:255)
	at com.influxdb.internal.AbstractQueryApi.query(AbstractQueryApi.java:207)
	at com.influxdb.internal.AbstractQueryApi.query(AbstractQueryApi.java:131)
	at com.influxdb.client.internal.QueryApiImpl.query(QueryApiImpl.java:939)
	at com.influxdb.client.internal.QueryApiImpl.query(QueryApiImpl.java:204)
	at com.influxdb.client.internal.QueryApiImpl.query(QueryApiImpl.java:147)

schema collision: cannot group integer and float types together

Ok, so you have fields that are different types. You have a few options for solving this:

Query a specific field

Query one field rather than all of them. This will return one value type for the _value column.

from(bucket:"energy_meter")
    |> range(start: -2d, stop: now())
    |> filter(fn: (r) => r._measurement == "snapshot")
    |> filter(fn: (r) => r._field == "example-field")
    |> group()
    |> sort(columns: ["_time"])
    |> last()

Cast fields to a specific type

Use toInt() or toFloat() to cast field values to a specific type. This will only work if all your field types are numeric. If the type of the _value column in the result doesn’t matter, you can use toString() to cast values to strings.

from(bucket:"energy_meter")
    |> range(start: -2d, stop: now())
    |> filter(fn: (r) => r._measurement == "snapshot")
    |> toFloat()
    |> group()
    |> sort(columns: ["_time"])
    |> last()

Thanks. The query doesn’t fail but none of the fields are set, they are all null except time (and activePower as it is a int that defaults to 0). If I do a queryRaw with the same flux, I am getting the following:

,result,table,_start,_stop,_time,_value,_field,_measurement
,_result,0,2025-08-16T18:58:55.299550133Z,2025-08-18T18:58:55.299550133Z,2025-08-18T18:58:55Z,20,in_t1,snapshot

Running the raw query again without last() I am getting:

,result,table,_start,_stop,_time,_value,_field,_measurement
,_result,0,2025-08-16T19:00:14.742764795Z,2025-08-18T19:00:14.742764795Z,2025-08-18T19:00:14Z,200,active_power,snapshot
,_result,0,2025-08-16T19:00:14.742764795Z,2025-08-18T19:00:14.742764795Z,2025-08-18T19:00:14Z,0,out_t2,snapshot
,_result,0,2025-08-16T19:00:14.742764795Z,2025-08-18T19:00:14.742764795Z,2025-08-18T19:00:14Z,0,out_t1,snapshot
,_result,0,2025-08-16T19:00:14.742764795Z,2025-08-18T19:00:14.742764795Z,2025-08-18T19:00:14Z,10,in_t2,snapshot
,_result,0,2025-08-16T19:00:14.742764795Z,2025-08-18T19:00:14.742764795Z,2025-08-18T19:00:14Z,20,in_t1,snapshot

This is an integration test that starts from an empty bucket FWIW. It only saves a single measure and I am trying the query that retrieves the last known snapshot.

Where are you seeing nulls in your results? Both of the result sets you sent have only non-null field values. Values are stored in the _value column.

Result represented as a table:

_start _stop _time _value _field _measurement
2025-08-16T18:58:55.299550133Z 2025-08-18T18:58:55.299550133Z 2025-08-18T18:58:55Z 20 in_t1 snapshot

Result represented as a table:

_start _stop _time _value _field _measurement
2025-08-16T19:00:14.742764795Z 2025-08-18T19:00:14.742764795Z 2025-08-18T19:00:14Z 200 active_power snapshot
2025-08-16T19:00:14.742764795Z 2025-08-18T19:00:14.742764795Z 2025-08-18T19:00:14Z 0 out_t2 snapshot
2025-08-16T19:00:14.742764795Z 2025-08-18T19:00:14.742764795Z 2025-08-18T19:00:14Z 0 out_t1 snapshot
2025-08-16T19:00:14.742764795Z 2025-08-18T19:00:14.742764795Z 2025-08-18T19:00:14Z 10 in_t2 snapshot
2025-08-16T19:00:14.742764795Z 2025-08-18T19:00:14.742764795Z 2025-08-18T19:00:14Z 20 in_t1 snapshot

All rows in both results have non-null values in the _value column. How are you parsing these results in your test?

All rows in both results have non-null values in the _value column. How are you parsing these results in your test?

Exactly, that’s my question. I am trying to map the result to a measurement, actually the exact same object that I used to ingest the data, i.e.:

queryApi.query(flux, EnergySnapshotMeasurement.class);

The fields in the returned EnergySnapshotMeasurement are null. When you look at last() it makes sense since it only returns an entry for one field.

To recap, I’d like to know how I can map a search result to a POJO, in particular with the flux that we’ve been working on here. I’ve searched the documentation and it’s far from obvious. Perhaps I am using the wrong querying API?

Ok, I think to parse the data the way you actually want to, you need to pivot the fields into columns in your query:

from(bucket:"energy_meter")
    |> range(start: -2d, stop: now())
    |> filter(fn: (r) => r._measurement == "snapshot")
    |> last()
    |> pivot(rowKey: ["_measurement"], columnKey: ["_field"], valueColumn: "_value")

This should return a single table with single row with all the last recorded values for each field. Something like this:

_measurement active_power out_t2 out_t1 in_t2 in_t1
snapshot 200 0 0 10 20

Thanks, this is getting close. Now the timestamp is not included (which shows in the table above).

Generally speaking, I wonder how folks are using queryApi(flux, T) as it looks really complicated to map the data back…

You just need to include _time in the rowKey argument of pivot(). This can be tricky though. If there is a nanosecond difference between timestamps, those are treated as distinct timestamps and pivot() will generate a pivoted row for each distinct _time and _measurement combination.

from(bucket:"energy_meter")
    |> range(start: -2d, stop: now())
    |> filter(fn: (r) => r._measurement == "snapshot")
    |> last()
    |> pivot(rowKey: ["_time", "_measurement"], columnKey: ["_field"], valueColumn: "_value")

Assuming all the timestamps are the same, this should give you:

_time _measurement active_power out_t2 out_t1 in_t2 in_t1
2025-08-18T19:00:14Z snapshot 200 0 0 10 20

Line protocol, the syntax used to write data to InfluxDB, was developed in InfluxDB v1. Line protocol better aligns with the structure of results returned by InfluxQL, the SQL-like query language for InfluxDB v1.

Flux, the query language introduced in InfluxDB v2, uses a different data model than both line protocol and InfluxQL. It returns data as a “stream of tables” where each table represents a group or series. Rather than structuring tables with multiple fields, each table, by default represents a single field. The field key is stored in the _field column and the field value is stored in the _value column. You can learn more about the Flux data model here.

You’re right that the Flux data model doesn’t map very well to the line protocol that produced it, but you can use pivot() to restructure the data to look more like the data that gets returned from and InfluxQL query and more closely resembles the line protocol that created it.

Thanks very much, this works. I am happy to use any mechanism to get the data back. I used Flux because I understand the v1 API is not the best bet going forward, but if I misunderstood it, I am happy to change to whatever makes more sense for my use case.