This content is dedicated to our next version. It is work in progress: its content will evolve until the new version is released.

Before that time, it cannot be considered as official.

BDM Query Response Formats

Understand the difference between legacy array-based format and standard REST shape format for BDM query responses.

Overview

Starting from version 2026.1, the response format for BDM queries has been standardized to follow REST API conventions.

Legacy Array-Based Format

In previous versions, all BDM queries returned results wrapped in JSON arrays:

// COUNT query
[42]

// Single entity
[{"persistenceId": 1, "name": "John Doe"}]

// Multiple entities
[{"persistenceId": 1}, {"persistenceId": 2}]

Standard REST Shape Format

The new standard format aligns with REST API best practices:

// COUNT query - scalar value wrapped in object
{"value": 42}

// Single entity - direct object
{"persistenceId": 1, "name": "John Doe"}

// Multiple entities - array (unchanged)
[{"persistenceId": 1}, {"persistenceId": 2}]

Format Comparison

Query Type Return Type Legacy Format Standard Format

COUNT

Long

[42]

{"value": 42}

AVG/MAX/MIN/SUM

Double

[55000.75]

{"value": 55000.75}

Single Entity

Entity

[{…​}]

{…​}

Multiple Entities

List

[{…​}, {…​}]

[{…​}, {…​}]

Empty Result (scalar)

null

[null]

{"value": null}

Empty Result (entity)

null

[]

{}

Configuration

The property bonita.runtime.business-data.serialization.standard-shape.enabled controls the response format.

Property

bonita.runtime.business-data.serialization.standard-shape.enabled

Type

Boolean

Default Value

true (from version 2026.1)

Scope

Tenant-level

Using Properties File

Edit bonita-tenant-community-custom.properties:

# Standard REST shape (default)
bonita.runtime.business-data.serialization.standard-shape.enabled=true

# Legacy array-based format
bonita.runtime.business-data.serialization.standard-shape.enabled=false

Using Environment Variable (Docker)

environment:
  BONITA_RUNTIME_BUSINESS_DATA_SERIALIZATION_STANDARD_SHAPE_ENABLED: "true"

See Docker environment variables for more details.

Pagination and Content-Range Header

When querying BDM entity lists via REST API, Bonita returns pagination metadata in the Content-Range HTTP header:

Content-Range: 0-9/150

Where 0 is the start index, 9 is the end index, and 150 is the total count of matching entities.

When Content-Range Appears

The Content-Range header is returned only for entity list queries that have a corresponding count query defined:

  • Provided queries (auto-generated): find, findBy{AttributeName} - count queries are created automatically

  • Custom list queries with a defined countFor{QueryName} query

  • Scalar queries (COUNT, AVG, MAX, MIN, SUM) - no Content-Range header

  • Single entity queries - no pagination needed

  • Custom list queries without count query - query works but no Content-Range

Defining Count Queries for Custom Queries

For custom queries returning lists, you must define a corresponding count query to enable pagination metadata:

<!-- Custom list query -->
<query name="findByStatus"
       content="SELECT e FROM Employee e WHERE e.status = :status"
       returnType="java.util.List">
    <queryParameter name="status" className="java.lang.String"/>
</query>

<!-- Count query for pagination (required for Content-Range) -->
<query name="countForFindByStatus"
       content="SELECT COUNT(e) FROM Employee e WHERE e.status = :status"
       returnType="java.lang.Long">
    <queryParameter name="status" className="java.lang.String"/>
</query>

Rules for count queries:

  • Name must be countFor{QueryName} (e.g., countForFindByStatus)

  • Return type must be java.lang.Long

  • Query parameters must match the base query exactly

Calling a COUNT query directly (e.g., ?q=countForFindByStatus) returns a scalar result with no Content-Range header. The header only appears when querying entity lists.

Migration Guide

Update your client code to handle the new format:

Before (Legacy):

// Scalar query
const response = await fetch('/API/bdm/businessData/Employee?q=countForFind');
const count = (await response.json())[0];  // Extract from array

// Single entity
const response = await fetch('/API/bdm/businessData/Employee?q=findByEmail&f=email=john@acme.com');
const employee = (await response.json())[0];  // Extract from array

After (Standard):

// Scalar query
const response = await fetch('/API/bdm/businessData/Employee?q=countForFind');
const count = (await response.json()).value;  // Access value property

// Single entity
const response = await fetch('/API/bdm/businessData/Employee?q=findByEmail&f=email=john@acme.com');
const employee = await response.json();  // Direct object

Option 2: Maintain Legacy Format

To keep backward compatibility, disable the standard format:

bonita.runtime.business-data.serialization.standard-shape.enabled=false

DAO methods in Groovy always return Lists, so server-side code doesn’t change. Only REST API consumers are affected.

UI Designer Migration

If you use BDM queries in UI Designer pages via External API variables, update your widget code to handle the new format.

Single Entity Query

Variable customerData with URL: ../API/bdm/businessData/org.acme.billing.Customer?q=findByCodeClient&f=codeClient=C001

Before (Legacy - array format):

// Legacy format returns: [{"persistenceId": 1, "nom": "Acme Corp", ...}]
var customer = $data.customerData[0];

if (customer) {
    return "Customer " + customer.nom + " (" + customer.codeClient + ") is active.";
} else {
    return "Customer not found.";
}

After (Standard - direct object):

// Standard format returns: {"persistenceId": 1, "nom": "Acme Corp", ...}
var customer = $data.customerData;

if (customer && customer.persistenceId) {
    return "Customer " + customer.nom + " (" + customer.codeClient + ") is active.";
} else {
    return "Customer not found.";
}

Scalar Query (COUNT, AVG, etc.)

Variable invoiceCount with URL: ../API/bdm/businessData/org.acme.billing.Invoice?q=countForFind

// Legacy format returns: [8]
var total = $data.invoiceCount[0];

// Standard format returns: {"value": 8}
var total = $data.invoiceCount.value;