ObjectNode jsonQuery() {

    if (!query.groupBy().isEmpty() && query.offset().isPresent()) {
      String message = "Currently ES doesn't support generic pagination "
              + "with aggregations. You can still use LIMIT keyword (without OFFSET). "
              + "For more details see https://github.com/elastic/elasticsearch/issues/4915";
      throw new UnsupportedOperationException(message);
    }

    final ObjectNode json = nodeFactory.objectNode();


    json.put("_source", false);
    json.put("size", 0);
    json.put("stored_fields", "_none_"); // avoid fetch phase

    query.filter().ifPresent(f -> json.set("query", Elasticsearch.constantScoreQuery(mapper, pathNaming, idPredicate).convert(f)));

    // due to ES aggregation format. fields in "order by" clause should go first
    // if "order by" is missing. order in "group by" is un-important
    final Set<Expression> orderedGroupBy = new LinkedHashSet<>();
    orderedGroupBy.addAll(query.collations().stream().map(Collation::expression).collect(Collectors.toList()));
    orderedGroupBy.addAll(query.groupBy());

    // construct nested aggregations node(s)
    ObjectNode parent = json.with(AGGREGATIONS);
    for (Expression expr: orderedGroupBy) {
      final String name = ((Path) expr).toStringPath();
      final String aggName = naming.apply(expr);
      final ObjectNode section = parent.with(aggName);
      final ObjectNode terms = section.with("terms");
      terms.put("field", name);

      mapping.missingValueFor(name).ifPresent(m -> {
        // expose missing terms. each type has a different missing value
        terms.set("missing", m);
      });

      query.limit().ifPresent(limit ->terms.put("size", limit));

      query.collations().stream()
              .filter(c -> c.path().toStringPath().equals(name))
              .findAny()
              .ifPresent(col -> terms.with("order").put("_key", col.direction().isAscending() ? "asc" : "desc"));

      parent = section.with(AGGREGATIONS);
    }

    for (Expression expr: query.projections()) {
      if (Visitors.isAggregationCall(expr)) {
        Call call = Visitors.toCall(expr);
        ObjectNode agg = nodeFactory.objectNode();
        String field = ((Path) call.arguments().get(0)).toStringPath();
        agg.with(toElasticAggregate(call)).put("field", field);
        parent.set(naming.apply(call), agg);
      }
    }

    // cleanup json. remove empty "aggregations" element (if empty)
    removeEmptyAggregation(json);
    return json;
  }
Used for constructing (possibly nested) Elastic aggregation nodes.
criteria/elasticsearch/src/org/immutables/criteria/elasticsearch/AggregateQueryBuilder.java