2026.0 to 2026.1
1. Variable Graph: Removal of V1 and Mandatory Use of V2
What changed
Variable Graph V1 is deprecated and will no longer be supported starting with release 2026.1. Variable Graph V2 has already existed for a long time and replaces V1 as the only supported implementation. While V2 is largely compatible with V1, its internal dependency resolution works differently and may lead to different behavior in edge cases.
Why it matters
Variable Graph V1 only considers dependencies that are directly visible within a single process. Dependencies introduced indirectly through shared libraries are not detected. This limitation prevents reliable dependency tracking and blocks further performance optimizations.
Variable Graph V2 resolves this by dynamically learning dependencies at runtime. During value evaluation, V2 records which variables and processes are actually accessed, including those coming from libraries. Dependency graphs are therefore built dynamically based on real execution paths, rather than statically by code analysis. This enables more accurate dependency tracking and is a prerequisite for future performance and scalability improvements.
Recommended actions
Developers must ensure that all processes use Variable Graph V2 before upgrading to release 2026.1. Existing implementations based on V1 should be reviewed and validated with V2, with special attention to edge cases where dependency resolution may differ due to the dynamic learning approach. Thorough testing is required to confirm that runtime behavior and results remain correct after the migration.
2. EntityModel: Unified row recalculation behavior for multi-row processing
What changed
Starting with release 2026.1, the EntityModel uses one unified row recalculation model for the main access paths that process several Entity rows one after another. This affects:
- UI multi-record views
entities.getRows- Entity Webservices
They now follow the same configurable behavior when row data is prepared and calculated instead of the previous behavior that was different for each access path.
The new behavior is controlled by the neonRowRecalculationMode preference setting.
LEGACY_RESET_FIELD_VALUES_ON_ROW_CHANGE resets field values before each row is processed and
keeps behavior close to the previous UI behavior. REUSE_FIELD_VALUES_ON_ROW_CHANGE reuses field
values between row changes and relies on dependency-based recalculation instead of resetting field values for every row.
Why it matters
Before release 2026.1, multi-row calculations could behave differently depending on the access path.
The UI reset field values before each row, while entities.getRows and Entity Webservices used
different preparation strategies. This could produce different calculated values for the same Entity
data, especially in valueProcess, displayValueProcess, and onValueChange logic that depended
on an empty, null, or freshly initialized field value.
This also affected the EntityModel's performance when loading large or complex EntityModel data sets.
The new model removes these access-path differences, but it also makes the selected recalculation
mode upgrade-relevant. LEGACY_RESET_FIELD_VALUES_ON_ROW_CHANGE is the compatibility mode and can
preserve logic that depends on implicit row resets, but it is slower because every row requires
additional recalculation work. REUSE_FIELD_VALUES_ON_ROW_CHANGE is the preferred
performance-oriented mode, but custom logic must be compatible with reused field values and
dependency-based updates.
Recommended actions
While it is recommended to have an understanding of the old behavior it isn't required for migration.
It is, however, essential to understand the new behavior and how it affects the EntityModel and its calculations.
Review custom Entity logic before switching to REUSE_FIELD_VALUES_ON_ROW_CHANGE. Pay special
attention to valueProcess and displayValueProcess implementations that only calculate a value
when $this.value is null, onValueChange logic that runs during row loading or recalculation,
and dependencies between calculated fields that are not explicitly declared.
(For example, situations like these: "We know that the process is executed whenever a new row is loaded")
Test affected Entities with several rows, not only with a single row. Compare business-critical
calculations in the UI, entities.getRows, and Entity Webservices, because these access paths now
share the same row recalculation model. If a calculation only works because a field was reset
implicitly before each row, update the logic so that it initializes the required value explicitly,
recalculates from the current row data, or declares the necessary dependency.
Use LEGACY_RESET_FIELD_VALUES_ON_ROW_CHANGE as a compatibility mode when existing custom logic
has not yet been reviewed or when preserving the previous reset behavior is required during
migration.
Use REUSE_FIELD_VALUES_ON_ROW_CHANGE after validating that the custom logic does not
depend on implicit row resets.
Even when using LEGACY_RESET_FIELD_VALUES_ON_ROW_CHANGE, the behavior is different from previous versions.
Field values are reset before each row, even in entities.getRows and Entity Webservices.
Example usage
The following implementation assumes that the field value starts as null. With
LEGACY_RESET_FIELD_VALUES_ON_ROW_CHANGE, the field is reset before each row and the calculation
runs for every row. With REUSE_FIELD_VALUES_ON_ROW_CHANGE, the field can keep the value from a
previous row, so the calculation may run only once and later rows may reuse a stale value.
import { result, vars } from "@aditosoftware/jdito-types";
import { DateUtils } from "DateUtils_lib";
import { TurnoverUtil } from "Turnover_lib";
if(vars.get("$this.value") == null)
{
let salesCurrYear = TurnoverUtil.calculateTurnoverForYear(
vars.get("$field.ORGANISATIONID"),
DateUtils.getCurrentYear()
);
result.string(salesCurrYear);
}
Code: Example of an implementation that must be reviewed before using REUSE_FIELD_VALUES_ON_ROW_CHANGE.
Cases like this can also be slower with LEGACY_RESET_FIELD_VALUES_ON_ROW_CHANGE than in previous
versions when they are used through entities.getRows or Entity Webservices. In these access paths,
$this.value is now reset to null for each row, so the process actually performs the calculation
for every row.
In previous versions, these access paths could skip the calculation because the previous row value
was still present. That was faster, but the result could be incorrect. With
LEGACY_RESET_FIELD_VALUES_ON_ROW_CHANGE, the result is correct because each row is calculated from
a reset value, but the additional processing can increase runtime. With
REUSE_FIELD_VALUES_ON_ROW_CHANGE, the behavior is faster and explicitly defined as the common
behavior for all Entity data access paths, but implementations that depend on a null start value
must be reviewed.
The REUSE_FIELD_VALUES_ON_ROW_CHANGE setting will still result in fewer calculations compared to the behavior
of Entity Webservices in previous versions.
3. Calendar Backend: Migration from EWS to ExchangeGraph
It is now necessary to migrate the calendar integration from the legacy EWS-based backend to the Graph-based backend (ExchangeGraph).
For detailed instructions, please refer to the Calendar Backend Migration Guide.
This is not relevant if you are using an on-premise installation of Exchange.
4. IndexSearch: Applies user permissions to server-side searches
What changed
Starting with release 2026.1, IndexSearch supports permission-aware searches in the server
context. When the compatibility flag is disabled, server-side searches apply public permissions
unless the query impersonates a user with setUser.
IndexSearch also provides setDisablePermissions(true) for JDito requests that must explicitly
run without permission filtering.
For backward compatibility, the new
legacyIndexsearchDoNotApplyPermissionsInServerContext project preference keeps the previous
server-side behavior. The flag is located under Preferences > Modules > Indexsearch, is enabled
by default, and will be removed in a future release.
User impersonation is only supported in the server context. Calling setUser in the client or
session context causes an error.
Why it matters
Before release 2026.1, server-side IndexSearch requests did not apply permissions. A JDito process running in the server context could therefore return index results that were not filtered for the user-related access rules of the calling use case. It was also not possible to impersonate a user for a server-side IndexSearch request.
This behavior differed from server-side entity access through JDito, where permissions were already applied and user impersonation was supported. If no user was specified for entity access, only public data was returned. IndexSearch now follows the same permission model for server-side searches.
Existing server-side searches can return fewer results after the legacy flag is disabled, because results are filtered by public permissions or by the permissions of the impersonated user.
Recommended actions
- Review all JDito processes that use IndexSearch in the server context.
- For user-related searches, set the user explicitly with
query.setUser(pUser)so that the search runs with the intended permissions. - For searches without a user context, verify that all required results are covered by public permissions.
- Use
query.setDisablePermissions(true)only for server-side JDito requests where bypassing permission filtering is intentional and covered by the process logic. - Disable
legacyIndexsearchDoNotApplyPermissionsInServerContextunderPreferences > Modules > Indexsearchafter affected server-side searches have been reviewed. - Test server-side searches with representative users before and after disabling the legacy flag.
Example usage
The following example shows a server-side JDito search that applies the permissions of the specified user.
import { indexsearch } from "@aditosoftware/jdito-types";
function searchIndexForUser(pPattern, pUser)
{
let query = indexsearch.createIndexQuery();
// Server-side impersonation applies this user's permissions to the search result.
query.setUser(pUser);
query.setPattern(pPattern);
return indexsearch.searchIndex(query);
}
Code: Example of a server-side IndexSearch request that impersonates a user.
5. IndexSearch: Collection parameter added to JDito indexsearch methods
What changed
Starting with release 2026.1, the new vector search is integrated into IndexSearch.
The JDito indexsearch methods that search, build patterns, resolve index
fields, or run indexers now support an additional parameter for the target
collection.
This affects the following methods:
searchIndexbuildPatternconvertFilterToPatternlookupIndexFieldrunIncrementalIndexerrunIndexer
Two constants define the allowed target collections:
indexsearch.COLLECTION_SEARCH_INDEXfor the standard search indexindexsearch.COLLECTION_VECTOR_INDEXfor the vector search index
The previous method signatures are deprecated. They still work for backward
compatibility, but they always use indexsearch.COLLECTION_SEARCH_INDEX.
Why it matters
Before release 2026.1, JDito IndexSearch calls implicitly addressed the standard search index. With the new vector search now available through the same IndexSearch API, the target collection determines which index, group definitions, and field mappings are used.
Code that omits the collection parameter can only use the standard search
index. This keeps existing processes compatible, but it also means that vector
searches, vector filters, and vector indexer jobs must use the new signatures
with indexsearch.COLLECTION_VECTOR_INDEX.
Pattern building, filter conversion, field lookup, search execution, and indexer runs must use the same target collection. If helper methods use the standard index while the search runs against the vector index, field mappings or filter patterns can be resolved for the wrong collection.
Recommended actions
- Review all JDito processes that call
searchIndex,buildPattern,convertFilterToPattern,lookupIndexField,runIncrementalIndexer, orrunIndexer. - Replace deprecated calls without a collection parameter with the new signatures that pass the target collection explicitly.
- Use
indexsearch.COLLECTION_SEARCH_INDEXfor existing standard index searches and standard indexer jobs. - Use
indexsearch.COLLECTION_VECTOR_INDEXfor vector searches, vector filters, vector field lookups, and vector indexer jobs. - Verify that helper calls such as
buildPattern,convertFilterToPattern, andlookupIndexFielduse the same collection as the relatedsearchIndexor indexer call. - Test migrated processes with representative search queries, filters, and index groups before relying on the new collection-specific behavior.
Example usage
import { indexsearch } from "@aditosoftware/jdito-types";
function searchOrganisationsByName(pSearchText)
{
const collection = indexsearch.COLLECTION_SEARCH_INDEX;
// Build the filter in the same collection context used for the search.
const nameFilter = indexsearch.buildPattern(
indexsearch.createPatternConfig()
.and(
indexsearch.createTerm(pSearchText)
.setEntityField("Organisation_entity.NAME")
),
collection
);
const query = indexsearch.createIndexQuery()
.setIndexGroups("Organisation")
.addFilter(nameFilter)
.setRows(10)
.setResultIndexFields(
indexsearch.FIELD_ID,
indexsearch.FIELD_TITLE,
indexsearch.FIELD_DESCRIPTION
);
return indexsearch.searchIndex(query, collection);
}
function rebuildOrganisationVectorIndex(pOrganisationId)
{
const config = indexsearch.createIncrementalIndexerRunConfig()
.group("Organisation")
.addUid(pOrganisationId);
// Vector indexer jobs must target the vector collection explicitly.
indexsearch.runIncrementalIndexer(
config,
indexsearch.COLLECTION_VECTOR_INDEX
);
}