Hi Serge,
-1
Thank you for the work on this release. The release mechanics are correct,
but the migration from 3.0.x fails on real 3.0.1 data, and several
regressions affect existing features.
I compared each item below with the unomi-root-3.0.1 tag, and all of them
are new in 4.0.0.
What I checked, and what passed:
- The sha512 checksums and the GPG signatures of the 3 archives are
correct. The key 16DF 7E56 7738 172F 945F 0402 7C19 D1FB BD1A B1DF is in
KEYS.
- The source zip matches the unomi-root-4.0.0 tag, apart from the files
that Maven generates or that git ignores. The staging source-release zip is
identical to the dist one.
- RAT reports 0 unapproved files.
- The build from the source zip with JDK 17 and Maven 3.9.11 succeeds, with
1681 unit tests and 0 failures.
BLOCKING ISSUES
1. Migration from 3.0.x
I ran the procedure of migrate-3.0-to-4.0.adoc on a Unomi 3.0.1 data set,
on Elasticsearch 9.4.3, with the binary from dist/dev.
a. unomi:migrate fails at the first reindex with "no mapping found in
bundles for: event.json". Before unomi:start, the only Unomi features
installed are unomi-base and unomi-startup, so
MigrationUtils.extractMappingFromBundles (MigrationUtils.java:305) finds no
mapping file. The mappings of unomi-persistence-elasticsearch-core and
cxs-geonames-services are both missing. In 3.0.1, the boot feature
unomi-kar installed these two bundles together with shell-commands.
Migrate16xToCurrentVersionIT cannot detect this, because Pax Exam installs
unomi-elasticsearch-core and the other features before the migration runs.
b. An index with no known mapping stops the migration with "no mapping
found in bundles for: generic.json"
(migrate-4.0.0-01-tenantDocumentIds.groovy:82,136). A 3.0.1 cluster always
has a context-clusternode index. A cluster that saved a Salesforce
connector configuration also has context-sfdcconfiguration, which takes the
same path.
c. The built-in definitions of 3.0.1 hide the built-in definitions of
4.0.0. The script initialize_tenant_and_audit_fields.painless:22,95 tests
scope == 'system', but no 3.0.1 item has this value. In 3.0.1, rules,
segments, scorings and goals keep the scope "systemscope" under
metadata.scope. The built-in condition types, action types and property
types have no scope. All the 3.x items go to the "default" tenant, and the
copy in that tenant wins over the copy in "system"
(MultiTypeCacheServiceImpl.java:126-139, RulesServiceImpl.java:281-304).
After the migration, the built-in definitions that changed in 4.0.0 keep
their 3.0.1 form, such as sessionAssigned, pastEventCondition,
profilePropertyCondition and booleanCondition. For example, a new visit
increments nbOfVisits but not totalNbOfVisits.
d. migrate-4.0.0-05-fixSystemItemIds.groovy:60 uses a term query on the
analysed field itemType. The camelCase types, such as conditionType,
actionType and propertyType, match nothing, and the log still says "Fixed".
e. migrate-4.0.0-10-tenantInitialization.groovy:109,112 writes a key file
that names the headers X-Unomi-Public-Key and X-Unomi-Key.
AuthenticationFilter.java:69 reads the header X-Unomi-Api-Key.
f. The tests on -systemitems and -geonames in the painless script are
always false. During the reindex, ctx._index is "<index>-cloned", and the
geonames index is "geonameentry". The migrated geonames entries go to
"default", but GeonamesServiceImpl reads them as "system".
g. The migration tests do not detect the issues above.
Migrate16xToCurrentVersionIT has 4.0.0 checks, and MigrationIT tests the
resume with synthetic scripts. But the 4.0.0 checks have these limits:
- The IT restores a 1.6.x snapshot and runs "unomi:migrate 1.6.0 true",
so no test starts from 3.x data. The snapshot has no context-clusternode
index.
- Pax Exam installs unomi-elasticsearch-core before the migration runs,
so the missing mapping bundles of item a do not fail the test.
- checkDefinitionsServiceObjects accepts a "system_" prefix or a test
tenant prefix. It does not check that the 4.0.0 version of a built-in
definition is the one in use, which is item c.
- checkDefaultTenantCreated checks that the tenant index exists, but it
makes no assertion when tenantService.getTenant() returns null and the
search finds no tenant document.
- checkMigratedData runs only on Elasticsearch.
I reproduced a to e. I found f in the code and did not reproduce it.
2. ElasticSearchPersistenceServiceImpl.purge(scope) deletes documents of
other scopes
At ElasticSearchPersistenceServiceImpl.java:2734, the call TermQuery.of(b
-> b.field("scope").value(scope).field("tenantId").value(...)) keeps only
the last field and value, because the builder overwrites them. The query
has no scope filter. DELETE /cxs/cluster/{scope} therefore deletes
documents of any type of the tenant, in all the indices with the index
prefix. In 3.0.1, the query filtered on the scope. The OpenSearch
implementation filters on the scope and on the tenant.
3. Scheduled tasks run only for the "system" tenant
Inside executeAsSystem, the persistence layer adds a filter on tenantId
"system" (ElasticSearchPersistenceServiceImpl.java:2982-3000). These tasks
therefore see only the "system" tenant:
- The profile-purge task (ProfileServiceImpl.java:363-376) runs
purgeProfiles, purgeSessionItems and purgeEventItems inside
executeAsSystem. The settings org.apache.unomi.profile.purge.*,
org.apache.unomi.session.purge.existTime and
org.apache.unomi.event.purge.existTime have no effect on the data of
"default" or of any other tenant.
- The daily recalculation of segments and scorings with relative dates
reads the cache of the "system" tenant (SegmentServiceImpl.java:1252-1290
and 1553). A tenant segment such as "visited in the last 7 days" is never
evaluated again.
- After a restart, initCamel runs with no execution context, so the
persistence layer uses the "system" tenant. The recurrent import and export
configurations of the tenants are not scheduled
(ProfileImportFromSourceRouteBuilder.java:104,
RouterCamelContext.java:317). A configuration comes back only when someone
saves it again.
RulesServiceImpl and TenantUsageServiceImpl loop over the tenants with
executeAsTenant, but these three tasks do not.
4. Single-tenant compatibility mode
- On a public path, the mode always creates a guest subject
(AuthenticationFilter.java:311). The mode still checks X-Unomi-Peer and the
IP address to accept a protected event, but the header gives no role. A 3.0
third-party server that sends X-Unomi-Peer loses these features:
- the profile merge on login,
- the profileId of the request body,
- the itemId of events,
- the cross-profile updateProperties, and the writes to systemProperties,
segments, scores and consents.
The Javadoc of the method says "Protected events require IP +
X-Unomi-Peer (like V2)". The 3.0.x backport of UNOMI-975 (commit 8824da595)
keeps X-Unomi-Peer as the trusted caller for the profile binding.
- Each public request calls TenantServiceImpl.getOrCreateTenant, which is
synchronized (TenantServiceImpl.java:117). The method reads the tenant from
the persistence layer with no cache (TenantServiceImpl.java:213-214), so
all the context.json requests go through one JVM lock and one Elasticsearch
round trip. Each public request already read the tenant with no cache
before my change for UNOMI-980 (https://github.com/apache/unomi/pull/861).
That change added the lock, and I can propose a fix.
OTHER REGRESSIONS
- GET /cxs/query/{type}/{property}, and POST on the same path with no
condition, count the data of all the tenants. The persistence layer adds
the tenant filter only when the request carries a condition
(ElasticSearchPersistenceServiceImpl.java:2500-2513). POST
/cxs/query/{type}/{property}/{metricTypes} never filters on the tenant
(getSingleValuesMetrics, line 2780). OpenSearch behaves the same way.
- Segment and scoring lists ignore the sort. SegmentServiceImpl merges the
sorted results into a HashMap, and the sort is a TODO
(SegmentServiceImpl.java:352, 1637 and 1720). In POST /cxs/segments/query
and POST /cxs/scoring/query, a missing limit or a limit of -1 gives a
negative toIndex to subList (line 1732). When at least one item matches,
the call answers 400 instead of the list.
- hoverEventCondition with only targetId matches every hover event, because
since UNOMI-828 the condition is an OR of targetId and targetPath. My
change for UNOMI-982 (https://github.com/apache/unomi/pull/864) makes a
branch with an absent parameter evaluate to true. The OR is therefore
always true when one of the two parameters is missing.
- context.json, eventcollector and client now set allowCredentials=false (
https://github.com/apache/unomi/pull/878). The shipped tracker sends
withCredentials=true, so a browser blocks the response on a cross-origin
setup.
- Unomi no longer reads UNOMI_HEALTHCHECK_ENABLED,
UNOMI_HEALTHCHECK_PROVIDERS and UNOMI_HEALTHCHECK_TIMEOUT. Commit cef067f2d
(UNOMI-919) removed their mapping from custom.system.properties.
configuration.adoc:1856-1862, index.adoc:180-181 and the quickstart
(5-min-quickstart.adoc:98) still document them.
- scheduler.nodeId defaults to "test-scheduler-node", and no environment
variable sets it. TaskLockManager compares the lock owner with this id
(TaskLockManager.java:240). Two nodes that keep the default can therefore
both take the lock and run the same task.
UNDOCUMENTED BREAKING CHANGES
migrate-3.0-to-4.0.adoc covers the API keys, the cookie, the mandatory
passwords, the conditions with no type, the router file paths and the
privileged REST roles. The guide does not document these changes:
- An admin call with Basic auth and no X-Unomi-Tenant-Id header runs in the
"system" tenant. It does not see the data that the migration put in
"default". The guide shows this call at lines 125-130. security.adoc calls
the header optional, but no page says what happens without it. An unknown
tenant id also falls back to "system" with only a warning. But
how-profile-tracking-works.adoc:126-128 says that the tenant must exist,
and that the request fails with 401.
- X-Unomi-Peer and thirdparty.cfg apply only in compatibility mode.
recipes.adoc and builtin-event-types.adoc say this for X-Unomi-Peer, but
configuration.adoc:165-222 still presents the thirdparty settings as
active. No page documents the tenant fields restrictedEventTypes and
authorizedIPs, which now control the protected events.
- These Java API changes are not mentioned:
- SchedulerService.getScheduleExecutorService() and
getSharedScheduleExecutorService() are removed.
- EventService.authenticateThirdPartyServer and isEventAllowed are
removed, and isEventAllowedForTenant replaces them.
- Parameter.getDefaultValue() now returns Object, and setDefaultValue now
takes an Object, which breaks binary compatibility.
- DefinitionsService.extractConditionsByType now returns a List instead
of a Set.
- The empty unomi-plugins-past-event artifact is removed.
PastEventConditionEvaluator and SourceEventPropertyConditionEvaluator moved
from unomi-plugins-baseplugin to the new unomi-plugins-advanced-conditions,
in the package org.apache.unomi.plugins.advancedconditions.conditions.
HoverEventConditionESQueryBuilder is removed.
- 18 shell commands are removed in favour of unomi:crud, among them
profile-list, rule-list and segment-view. shell-commands.adoc:99-189 still
lists them, and tutorial.adoc:226 and 274 still use them.
- Document ids now start with the tenant id: <tenantId>_<itemId>, or
<tenantId>_<itemId>_<itemType> for the system items. This change affects
any script or dashboard that reads _id.
- UNOMI_GRAPHQL_FEATURE_ACTIVATED is removed, and only the graphql
distributions or the cdp-graphql-feature enable GraphQL. In the health
check, org.apache.unomi.healthcheck.cfg becomes
org.apache.unomi.healthcheck-elasticsearch.cfg, and
httpClient.trustAllCertificates becomes esHttpClient.trustAllCertificates.
configuration.adoc still names the old file. POST /cxs/profiles with no
itemId now answers 400.
- condition-validation.adoc:187 says that the REST client receives the
validation message. The client receives {"errorMessage":"badRequest"}.
MINOR, NOT BLOCKING
The staging repository holds unomi-4.0.0-buildcache.jar. Unomi 3.0.1 did
not publish this artifact.
Regards,
Jonathan
On Sun, Oct 4, 2026 at 4:19 PM Mohamed-Tayeb BENTERKI <[email protected]>
wrote:
> +1 🚀
>
> On Sun, 4 Oct 2026 at 14:18, Serge Huber <[email protected]> wrote:
>
> > Hi all,
> >
> > I (finally !) submit Apache Unomi 4.0.0 to your vote.
> >
> > This release has been a long time coming. The work began as the "Unomi
> > Version 3" plan (UNOMI-875, February 2025), with implementation from
> March
> > 2025 — months before Unomi 3.0.0 was cut in November 2025. By September
> > 2025 the tree was already versioned 3.1.0-SNAPSHOT; 3.0.0 (and later
> > 3.0.1 in July 2026) was a first slice, while this line continued in
> > parallel. As the scope grew — multi-tenancy, a second persistence
> backend,
> > a large platform refresh, and a much stronger REST and operations story —
> > we renamed it 4.0.0 so the version number matches the size of the change.
> > This candidate is the result.
> >
> > A short view of what 4.0 brings:
> >
> > Features
> > - Multi-tenancy, with a compatibility mode for single-tenant setups
> > - OpenSearch as a persistence option alongside Elasticsearch
> > - Cluster-aware scheduling, unified tenant caching, and a unified shell
> > CRUD
> > - Tenant usage through the REST API, plus event retention purge
> > - Request tracing, and a rangeQuery API on persistence
> > - REST V2 compatibility mode, better input validation, and clearer error
> > responses
> > - Migration scripts from 3.x, including resume-safe 3.1/4.0 migration
> >
> > Platform and libraries
> > - Newer Karaf, Elasticsearch, OpenSearch, and Log4j
> > - Further third-party updates (UNOMI-990 and related)
> > - Stricter startup (explicit admin and health-check passwords) and
> tighter
> > control of privileged REST, Groovy actions, and import/export
> > - Build cache, BOM coverage for advanced conditions, and a published
> > distribution again
> >
> > Fixes
> > - Condition validation and matching (optional parameters, unresolved
> > types, segment/scoring save)
> > - Context/profile binding, CSV import, persona sessions, health-check
> > concurrency
> > - Scheduler locks and recovery in a cluster
> > - Tracker WAB, visit/session counts, logging, and several CI/IT flakes
> >
> > Documentation and developer experience
> > - Manual refresh with diagrams, Javadoc, and OpenAPI filled from Javadoc
> > - Pluggable persistence ITs, better IT progress/tooling, improved
> build.sh,
> > and contributor guidelines
> >
> > Tests grew a lot compared with 3.0.1. Annotated test methods went from
> > about
> > 280 to about 2,130. Most of that is new unit tests: roughly 90 unit tests
> > in
> > 15 classes became about 1,640 in 120 classes, while integration tests
> rose
> > from about 190 to about 490. The bulk of the unit work uses a new
> in-memory
> > persistence manager (UNOMI-881), so core services can be tested without
> > Elasticsearch or OpenSearch. That harness also has its own suite (about
> 200
> > tests on the in-memory store), and more than a dozen service tests
> > (profiles,
> > events, rules, segments, scheduler, conditions, and others) now run on
> it.
> >
> > Full JIRA notes (about 79 issues):
> >
> >
> https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12319220&version=12356384
> >
> > Staging repository:
> > https://repository.apache.org/content/repositories/orgapacheunomi-1054/
> >
> > Source:
> >
> >
> https://repository.apache.org/content/repositories/orgapacheunomi-1054/org/apache/unomi/unomi-root/4.0.0/unomi-root-4.0.0-source-release.zip
> >
> > Convenience binaries:
> >
> >
> https://repository.apache.org/content/repositories/orgapacheunomi-1054/org/apache/unomi/unomi/4.0.0/
> >
> > Git tag:
> > unomi-root-4.0.0
> >
> > Dist:
> > https://dist.apache.org/repos/dist/dev/unomi/4.0.0/
> >
> > Please vote to approve this release:
> >
> > [ ] +1 Approve the release
> > [ ] -1 Don't approve the release (please provide specific comments)
> >
> > This vote will be open for at least 72 hours, or until the necessary
> > number of binding votes (3 +1) is reached.
> >
> > Please let me know if you have any questions.
> >
> > Thanks,
> > Serge
> >
>
--
[image: Dev]
*Jonathan SINOVASSIN NAIK*FULLSTACK DEVELOPPER
[image: linkedin]
<https://app.signitic.com/l/ACdRwwAAABAAAAAABhZkJQ.t7MdoRPQji8> [image: web]
<https://app.signitic.com/l/ACdRwwAAABYAAAAABhZkJQ.8yvongia6Lk> [image:
github]
<https://app.signitic.com/l/ZGhoWnNIeVJXV051NzBOYmZMMTFaUT09-NlpUZ01IaXcvRTNYWnk5bEtRWEZCdz09>
[image: Bannière Jahia avec badges G2 - Leader - Best Relationship - Best
Results - Best Usability]
<https://app.signitic.com/l/ACdRwwAAABUAAo7LBhZkJQ.r8W_TAY9ofY>