Master Detail
The adapter supports AG Grid's Master / Detail view, allowing you to nest grids inside rows.
Enabling Master Detail
To enable Master/Detail, set .masterDetail(true) in the builder.
Unlike standard configuration, Master/Detail settings are grouped into a MasterDetailParams object. You must configure what to display (the detail entity class and columns) and how to link the detail records to the master record.
Configuration Parameters
| Method | Type | Required | Description |
|---|---|---|---|
masterDetail | boolean | Yes | Enables the functionality. |
primaryFieldName | String | Yes | The ID field name of the Master entity. |
masterDetailParams | MasterDetailParams | Yes* | Configuration object containing detail class, columns, and joining logic. |
*Required unless using dynamic params.
Relationship Mapping
Inside MasterDetailParams, you must define how the adapter finds the detail records for a specific master row. Provide one of the following:
- Reference Field (
detailMasterReferenceField): Use if the Detail entity has a@ManyToOnemapping to the Master. - ID Field (
detailMasterIdField): Use if the Detail entity only holds the raw Foreign Key ID. - Custom Predicate (
createMasterRowPredicate): Use for complex joining logic.
QueryBuilder.builder(MasterEntity.class, em)
.masterDetail(true)
.primaryFieldName("id") // Master ID
// Group Detail Configuration
.masterDetailParams(
QueryBuilder.MasterDetailParams.builder()
.detailClass(DetailEntity.class)
.detailColDefs(
ColDef.builder().field("detailId").build(),
ColDef.builder().field("amount").build()
)
// Define Relationship (Choose One)
.detailMasterReferenceField("parentEntity")
// .detailMasterIdField("parentId")
.build()
)
.build();
Lazy vs Eager loading Detail Rows
The adapter supports two strategies for loading detail data: Lazy (default) and Eager.
Lazy Loading (Default)
masterDetailLazy(true)
In this mode, detail data is not sent with the master rows.
When a user expands a row in AG Grid, the grid sends a separate server-side request.
You should expose a separate endpoint that calls getDetailRowData(masterRow).
Eager Loading
masterDetailLazy(false)
In this mode, the adapter fetches detail rows immediately for every master row returned and embeds them directly into the response JSON. This reduces HTTP requests but increases payload size and backend load.
By default, the adapter optimizes eager loading using a Batch Fetching strategy. It executes exactly 2 database queries per request, regardless of page size:
- One query to fetch the Master rows.
- One query to fetch all Detail rows for those masters (using an
INclause).
The optimization above is disabled if you use dynamicMasterDetailParams or a custom createMasterRowPredicate.
In these dynamic scenarios, the adapter must fall back to the N+1 strategy, executing a separate database query for each master row returned. Use dynamic configuration with caution on large page sizes.
Requirements:
- You must provide
.masterDetailRowDataFieldName(String)on the main builder. - This field name must not exist in the
detailColDefs.
QueryBuilder.builder(MasterEntity.class, em)
.masterDetail(true)
.masterDetailLazy(false) // Turn off lazy loading
.masterDetailRowDataFieldName("detailRows") // JSON key for nested list
.primaryFieldName("id")
.masterDetailParams(
QueryBuilder.MasterDetailParams.builder()
.detailClass(DetailEntity.class)
.detailColDefs(
ColDef.builder().field("detailId").build()
)
.detailMasterReferenceField("parentEntity")
.build()
)
.build();
JSON Response example:
[
{
"id": 1,
"name": "Master Row A",
"detailRows": [ // populated automatically
{ "detailId": 100, "amount": 50 },
{ "detailId": 101, "amount": 25 }
]
}
]
Custom Detail Condition
If standard ID or Reference mapping is insufficient (e.g., for composite keys or complex conditional joining), you can define exact linking logic using createMasterRowPredicate.
This function provides access to the JPA CriteriaBuilder, the detail entity Root, and the raw masterRow data map, allowing you to construct any valid JPA Predicate to filter the detail records.
QueryBuilder.builder(Trade.class, entityManager)
.colDefs(...)
.masterDetail(true)
.primaryFieldName("id")
.masterDetailParams(
QueryBuilder.MasterDetailParams.builder()
.detailClass(...)
.detailColDefs(...)
.createMasterRowPredicate((cb, detailRoot, masterRow) -> {
// detail will have all the trades that have the same submitter
var submitterObj = (Map<String, Object>) masterRow.get("submitter");
if (submitterObj == null || submitterObj.isEmpty()) {
return cb.or();
}
Long submitterId = Optional.ofNullable(submitterObj.get("id")).map(String::valueOf).map(Long::parseLong).orElse(null);
Path<?> path = Utils.getPath(detailRoot, "submitter.id");
if (submitterId == null) {
return cb.isNull(path);
} else {
return cb.equal(path, submitterId);
}
})
.build()
)
.build();
Dynamic Detail Definitions
For advanced use cases, the structure of the detail grid can change based on the data in the master row. For example, a "Vehicle" master row might show "Car Details" or "Boat Details" depending on the vehicle type.
You can provide a function to resolve the MasterDetailParams dynamically at runtime using dynamicMasterDetailParams.
Resolution This function receives the masterRow (as a Map) as an argument, allowing you to inspect values to decide which Detail Class, Columns, and Relationships to use.
QueryBuilder.builder(Vehicle.class, em)
.masterDetail(true)
.primaryFieldName("id")
// Dynamic Configuration
.dynamicMasterDetailParams(masterRow -> {
String type = (String) masterRow.get("type");
if ("CAR".equals(type)) {
return QueryBuilder.MasterDetailParams.builder()
.detailClass(CarDetail.class)
.detailColDefs(
ColDef.builder().field("wheels").build(),
ColDef.builder().field("engine").build()
)
.detailMasterReferenceField("vehicle")
.build();
} else {
return QueryBuilder.MasterDetailParams.builder()
.detailClass(BoatDetail.class)
.detailColDefs(
ColDef.builder().field("propeller").build(),
ColDef.builder().field("sails").build()
)
.detailMasterReferenceField("vehicle")
.build();
}
})
.build();
If you enable masterDetail, you must provide either the static definition (masterDetailParams) OR the dynamic definition (dynamicMasterDetailParams).