Best approach to return hierarchical/grouped JSON from AEM Content Fragments organized in subfolders | Community
Skip to main content
Level 1
April 15, 2026
Question

Best approach to return hierarchical/grouped JSON from AEM Content Fragments organized in subfolders

  • April 15, 2026
  • 2 replies
  • 36 views

Hi all,

I'm working on an AEM 6.5 project and need to serve Content Fragment data as a hierarchical JSON response grouped by subfolder, not just a flat list. Looking for advice on whether my approach is sound or if there's a better way.

Requirement

I have content organized in subfolders under a parent folder in the DAM. Each subfolder represents a "section" of a page, and each section contains multiple content fragments of the same model.

DAM structure:

/content/dam/.../parent-folder/
  ├── section-alpha/
  │   ├── item-1   (CF)
  │   └── item-2   (CF)
  ├── section-beta/
  │   └── item-1   (CF)
  ├── section-gamma/
  │   └── item-1   (CF)
  └── section-delta/
      ├── item-1   (CF)
      ├── item-2   (CF)
      └── item-3   (CF)

Expected JSON response (strict contract with frontend):

{
  "page_name": {
    "section_alpha": {
      "items": [
        {
          "field_a": "value",
          "field_b": 2,
          "field_c": ["tag1", "tag2"],
          "field_d": "some expression",
          "field_e": "Title text",
          "field_f": "Description text",
          "image": { "_publishUrl": "https://..." },
          "field_g": "link",
          "flag_1": true,
          "flag_2": false,
          "field_h": "config_value_1",
          "field_i": "config_value_2"
        }
      ],
      "total_count": 2
    },
    "section_beta": {
      "items": [...],
      "total_count": 1
    },
    "section_gamma": {
      "items": [...],
      "total_count": 1
    },
    "section_delta": {
      "items": [...],
      "total_count": 3
    }
  }
}

 

The problem

AEM GraphQL persisted queries return a flat list:

{
  "data": {
    "myModelList": {
      "items": [
        { "_path": "/.../section-alpha/item-1", ... },
        { "_path": "/.../section-alpha/item-2", ... },
        { "_path": "/.../section-beta/item-1", ... },
        ...
      ]
    }
  }
}

 

There's no native way in AEM GraphQL to:

  1. Group items by their parent subfolder in the response
  2. Wrap each group in a named key with a computed count
  3. Nest the groups under a parent object

The STARTS_WITH filter on _path returns all CFs under a folder, but as a flat array with no subfolder grouping.

My proposed solution

CF Model: Single flat model with all fields (no fragment references needed since the contract is flat).

Persisted query: One query that fetches all items under the parent folder using STARTS_WITH, including _path in the response.

query($path: ID!) {
  myModelList(
    sort: { priority_field: ASC }
    filter: {
      _path: {
        _expressions: { value: $path, _operator: STARTS_WITH }
      }
    }
  ) {
    items {
      _path
      field_a
      field_b
      # ... all other fields
    }
  }
}

Custom Sling Servlet that:

  1. Makes a single internal call to the persisted query with path = /parent-folder/
  2. Parses the flat GQL response
  3. Groups items by extracting the subfolder name from each item's _path (e.g., _path = .../section-alpha/item-1 → group key = section_alpha)
  4. Strips _path from each item
  5. Assembles the hierarchical JSON with computed total_count per group
  6. Returns the final response

My questions

  1. Is a custom servlet for post-processing the right approach? Or is there a way to achieve subfolder-based grouping directly in AEM GraphQL that I'm missing?

  2. Single broad query vs. N queries per subfolder? I'm leaning toward one broad STARTS_WITH query on the parent + Java-side grouping (1 HTTP call) rather than N separate queries per subfolder. Any pitfalls with large result sets?

  3. Alternative: Store the entire structure as raw JSON in a single CF using a JSON field type. This avoids the complexity entirely but loses structured authoring, field-level validation, and content reusability. Anyone found a middle ground?

  4. Has anyone built a reusable "CF aggregation" pattern that groups content fragments by folder structure in the response? Would love to see if there's a community-standard approach.

Thanks for any pointers!

2 replies

Level 1
April 16, 2026

One question: You are setting up the GraphQL endpoint to consume it by servlet within AEM , no external request comes to persistent Graphql endpoint? 

If you are consuming all the CFs only by AEM servlet, why don’t you directly read(loop thru) the CFs based on parent path and you consolidate it by group in a way you want and return the JSON response. Just a suggestion. 

Level 1
May 6, 2026

Custom Sling servlet: Yes, this is the right pattern.
AEM GraphQL is intentionally a thin data layer with no support for grouping, aggregation, or computed fields. A servlet as an orchestration/transformation layer is the standard approach for contract-driven response shaping. Keep it focused: query → group → serialize.
Single broad query vs. N queries: Go with one STARTS_WITH query + Java-side grouping.
A couple of things to watch out for:
    •    Pagination cap: AEM GraphQL defaults to 100 items max on list queries. If your dataset could exceed that, use cursor-based pagination (first/after args on myModelList) or you’ll silently get incomplete data.
    •    Path extraction: Parsing the subfolder name from _path by index is fragile if DAM paths ever change depth. Better to make the parent path configurable (OSGi config) and extract the segment relative to it.
    •    Caching: Add a Cache-Control header on the servlet response. Even a short TTL via Dispatcher (e.g., 5 min) avoids repeated internal calls for content that doesn’t change frequently.
Raw JSON field: I’d avoid it.
You already identified the downsides — you lose structured authoring, field validation, and fragment reusability. It’s technical debt disguised as simplicity.
One pattern worth adding — a separate adapter/grouping class:
Rather than doing the grouping logic inside the servlet itself, extract it into a dedicated Java class:

public Map<String, SectionData> groupBySubfolder(List<CFItem> items, String parentPath) {
    return items.stream().collect(
        Collectors.groupingBy(item -> extractSectionKey(item.getPath(), parentPath))
    );
}
 

This keeps the servlet clean (just orchestrates the call and writes JSON) and makes the grouping logic independently unit-testable without any servlet context.
The pagination cap is the most common silent failure point with this pattern — worth verifying that first before anything else.