Columns
In order to define which columns should be returned to the client, we need to use ColDefs objects. Each column that we want to include in the AG Grid response must be explicitly defined in the ColDefs.
Defining Columns
Each column is defined using a ColDef object.
| Property | Type | Default | Description |
|---|---|---|---|
field (required) | SingularAttribute / FieldPath / ComputedField | — | Where the column reads its value, passed to ColDef.builder(...): a JPA metamodel attribute (e.g. Trade_.price), a FieldPath for nested paths, or a ComputedField for a database expression. |
sortable | boolean | true | Enables or disables sorting. |
filter | IFilter | — | Defines the filter type (no default — a column has no filter unless one is set). Supports: ✅ Custom IFilter implementations ✅ Built-in filters (e.g., AgNumberColumnFilter) ⚠️ Omit .filter(...) to leave the column without a filter |
enableValue | boolean | false | Set to true if you want to be able to aggregate by this column. |
enableRowGroup | (boolean, Function<String, T>) | false | Enable row grouping on this column. Because AG Grid sends group keys as strings, you must also pass a converter from the string group key to the column type T, e.g. .enableRowGroup(true, BigDecimal::new). |
enablePivot | boolean | false | Set to true if you want to be able to pivot by this column. |
allowedAggFuncs | Set<AggregationFunction> | All available | Defines allowed aggregation functions. |
Example Usage
var priceColumn = ColDef.builder(Entity_.price)
.sortable(true)
.filter(AgSetColumnFilter.forNumber())
.allowedAggFuncs(AggregationFunction.avg, AggregationFunction.count)
.build();
// no .filter(...) -> this column has no filter
var nameColumn = ColDef.builder(Entity_.name)
.sortable(false)
.build();
QueryBuilder<Entity, Long, Void> queryBuilder = QueryBuilder.builder(Entity.class, Entity_.id, entityManager)
.colDefs(priceColumn, nameColumn)
.build();
Inherited columns
Attributes declared on a @MappedSuperclass are typed against that superclass in the generated metamodel, and they are accepted everywhere the adapter takes a column or a metamodel attribute — including the primary field.
@MappedSuperclass
public abstract class AuditedRecord {
@Id
private Long recordId;
private String owner;
}
@Entity
public class Invoice extends AuditedRecord {
private String invoiceNumber;
}
// AuditedRecord_.recordId is a SingularAttribute<AuditedRecord, Long>, the grid queries Invoice
QueryBuilder<Invoice, Long, Void> queryBuilder = QueryBuilder.builder(Invoice.class, AuditedRecord_.recordId, entityManager)
.colDefs(
ColDef.builder(AuditedRecord_.recordId).build(),
ColDef.builder(AuditedRecord_.owner).filter(AgSetColumnFilter.forString()).build(),
ColDef.builder(Invoice_.invoiceNumber).build()
)
.build();
The same holds for FieldPath hops, quickFilterSearchInFields, the tree-data and master-detail attributes, and for a ComputedField<AuditedRecord, T> used in a grid over Invoice.
Computed columns
A column does not have to map to an entity attribute. ComputedField builds the column from a JPA Expression, so the value is produced by the database.
| Property | Type | Description |
|---|---|---|
name | String | The column name AG Grid sends and the field name in the response. |
javaType | Class<T> | The type the expression evaluates to. |
expressionFunction | BiFunction<CriteriaBuilder, Root<? extends E>, Expression<T>> | Builds the expression for a given query root. |
The expression is resolved wherever the column is used, so a computed column behaves like a mapped one:
selecting, filtering, sorting, row grouping, aggregation, pivoting, quick filter, advanced filter and
supplySetFilterValues all work on it.
ComputedField<Trade, String> valueBand = ComputedField.<Trade, String>builder()
.name("valueBand")
.javaType(String.class)
.expressionFunction((cb, root) -> cb.<String>selectCase()
.when(cb.greaterThan(root.get(Trade_.currentValue), BigDecimal.ZERO), "POSITIVE")
.otherwise("NON_POSITIVE"))
.build();
QueryBuilder<Trade, Long, Void> queryBuilder = QueryBuilder.builder(Trade.class, Trade_.tradeId, entityManager)
.colDefs(
ColDef.builder(Trade_.tradeId).build(),
ColDef.builder(valueBand)
.filter(AgSetColumnFilter.forString())
.enableRowGroup(true, key -> key)
.build()
)
.build();
Value Change is currentValue - previousValue, Value Band is a CASE over those same two columns and
Portfolio / Book concatenates two text columns. None of the three exists on the entity.
- try filtering or sorting on a computed column, or drag
Value Bandinto the row group panel - the
Value Bandset filter values come fromsupplySetFilterValues, read straight from the expression - Source code for this grid available here
- Backend source code available here
Dot notation
The adapter fully supports columns on nested/related entities. The path is built with FieldPath, chaining .to(...) through the mapped relationships. The resulting column name in the AG Grid response still uses dot notation (e.g. submitter.id).
// submitter.id
ColDef.builder(FieldPath.of(Trade_.submitter).to(Submitter_.id)).build()
// parentTrade.parentTrade.tradeId
ColDef.builder(FieldPath.of(Trade_.parentTrade).to(Trade_.parentTrade).to(Trade_.tradeId)).build()
Each hop must correspond to a mapped relationship (e.g., @ManyToOne, @OneToOne) within your JPA Entity.
It uses LEFT JOINS to join the table.
Trade Idis from main tableSubmitter IdusesFieldPath.of(Trade_.submitter).to(Submitter_.id), referencing thesubmitterentitySubmitter Deal IdusesFieldPath.of(Trade_.submitterDeal).to(SubmitterDeal_.id), referencing thesubmitterDealentityParent Trade IdusesFieldPath.of(Trade_.parentTrade).to(Trade_.tradeId), referencing theparentTradeentity (same table)Parent Trade -> Parent Trade IdusesFieldPath.of(Trade_.parentTrade).to(Trade_.parentTrade).to(Trade_.tradeId), referencing parent's parent trade- Source code for this grid available here
- Backend source code available here
Suppress Field Dot Notation - Flat data
By default, dot notation fields are returned as nested JSON objects to align with AG Grid's default behavior (check response body in previous grid).
To return flat keys (e.g., "category.name": "Value"), you must enable suppressFieldDotNotation in the QueryBuilder.
QueryBuilder<Entity, Long, Void> queryBuilder = QueryBuilder.builder(Entity.class, Entity_.id, entityManager)
.colDefs(
// ...col defs
)
.suppressFieldDotNotation(true)
.build();
If you enable suppressFieldDotNotation(true) in the backend, you must also set suppressFieldDotNotation to true in your AG Grid options on the frontend to ensure the grid treats dots as literal characters.