apache/skywalking · error · IllegalExpressionException

LATEST can only be used in time series result.

Error message

LATEST can only be used in time series result.

What it means

IllegalExpressionException from AggregationOp: the LATEST aggregation only has meaning over a time-ordered series, so it requires the input ExpressionResult to be of type TIME_SERIES_VALUES. Applying LATEST (e.g. via latest(...)) to a SINGLE_VALUE, SORTED_LIST or RECORD_LIST result is rejected before evaluation.

Source

Thrown at oap-server/mqe-rt/src/main/java/org/apache/skywalking/mqe/rt/operation/AggregationOp.java:52

import org.apache.skywalking.oap.server.core.query.mqe.MQEValues;

public class AggregationOp {
    public static ExpressionResult doAggregationOp(ExpressionResult result,
                                                   int opType) throws IllegalExpressionException {
        switch (opType) {
            case MQEParser.AVG:
                return aggregateResult(result, mqeValues -> mqeValues.getValues()
                                                                     .stream()
                                                                     .filter(mqeValue -> !mqeValue.isEmptyValue())
                                                                     .flatMapToDouble(mqeValue -> DoubleStream.of(
                                                                         mqeValue.getDoubleValue()))
                                                                     .average());
            case MQEParser.COUNT:
                return aggregateResult(result, mqeValues -> OptionalDouble.of(
                    mqeValues.getValues().stream().filter(mqeValue -> !mqeValue.isEmptyValue()).count()));
            case MQEParser.LATEST:
                if (result.getType() != ExpressionResultType.TIME_SERIES_VALUES) {
                    throw new IllegalExpressionException("LATEST can only be used in time series result.");
                }
                return selectResult(result, mqeValues -> Streams.findLast(mqeValues.getValues()
                                                                                   .stream()
                                                                                   .filter(mqeValue -> !mqeValue.isEmptyValue())));
            case MQEParser.MAX:
                return selectResult(result, mqeValues -> mqeValues.getValues()
                                                                  .stream()
                                                                  .filter(mqeValue -> !mqeValue.isEmptyValue())
                                                                  .max(Comparator.comparingDouble(
                                                                      MQEValue::getDoubleValue)));
            case MQEParser.MIN:
                return selectResult(result, mqeValues -> mqeValues.getValues()
                                                                  .stream()
                                                                  .filter(mqeValue -> !mqeValue.isEmptyValue())
                                                                  .min(Comparator.comparingDouble(
                                                                      MQEValue::getDoubleValue)));
            case MQEParser.SUM:
                return aggregateResult(result, mqeValues -> OptionalDouble.of(mqeValues.getValues()

View on GitHub (pinned to 102af09b4a)

Solutions

  1. Reorder the expression so latest() directly wraps the time-series metric before type-changing ops: latest(metric.sum(dt5m))
  2. Replace latest() with an appropriate function for list inputs (e.g. topN or takeLast-style op per the MQE docs for your result shape)
  3. Inspect the inner expression's result type in the UI error context and adjust until the operand is TIME_SERIES_VALUES
  4. If you need the most recent value of a labeled series, aggregate labels after latest(), not before

Example fix

# before
latest(aggregate_labels(service_resp_time.irate(dt5m), AVG))
# after
aggregate_labels(latest(service_resp_time.irate(dt5m)), AVG)
Defensive patterns

Strategy: validation

Validate before calling

// latest() requires a TIME_SERIES_VALUES operand — ensure no type-changing op (topn/sort/relabel) sits inside it
boolean operandIsTimeSeries(ExpressionResult inner) { return inner.getType() == ExpressionResultType.TIME_SERIES_VALUES; }

Type guard

boolean canApplyLatest(org.apache.skywalking.oap.server.core.query.mqe.ExpressionResult r) {
    return r.getType() == org.apache.skywalking.oap.server.core.query.mqe.ExpressionResultType.TIME_SERIES_VALUES;
}

Prevention

When it happens

Trigger: An MQE expression where latest() is applied to a non-timeseries operand, e.g. 'latest(aggregate_labels(metric, SUM))' if the inner result is no longer time-series, or latest() on top of a relabel/sort step that changed the result type to a list of records.

Common situations: Chaining MQE functions that change result type (topN/sort produce SORTED_LIST; aggregateLabels on a single point can collapse the series); users expecting latest() to mean 'last element' of any list.

Related errors


AI-assisted analysis of apache/skywalking@102af09b4a (2026-08-14). Data as JSON: /api/errors/eb8bace52b7483c4. Report an issue: GitHub.