8.2 Changelog

This page lists all the changes introduced in 8.2 releases. See also the release notes and the migration guide.

8.2.4 (2026-10-07)

Change

  • Upgrade backend dependencies

    Details

    Here is the list of backend dependencies upgraded:

    • Shiro from 2.1.0 to 2.2.1

    • pac4j from 6.5.3 to 6.5.9

    • buji-pac4j from 9.1.1 to 9.1.2

    • Tomcat from 10.1.57 to 10.1.60

    • Jackson from 2.21.5 to 2.21.7

    • Groovy from 4.0.32 to 4.0.33

    • Slf4j from 2.0.18 to 2.0.20

    • Ldaptive from 2.5.0 to 2.5.1

    • Jansi from 4.1.0 to 4.1.3

    • Swagger from 2.2.50 to 2.2.55

    • ByteBuddy from 1.18.10 to 1.18.14

    • Resteasy from 6.2.16 to 6.2.19

    • Graal from 25.0.3 to 25.0.4.1.1

    • Hibernate from 6.6.53 to 6.6.58

    • Hibernate Validator from 8.0.3 to 8.0.5

    • Jsoup from 1.22.2 to 1.23.2

    • Redisson from 4.3.1 to 4.7.0

Fix

  • Fix forceTitle view param and canEdit dashlet attribute in popup editor for all views

    Details

    The forceTitle action param was only applied when opening records from a grid dashlet popup. It is now applied for all views (grid, cards, kanban, calendar, gantt) opening the popup editor.

    A panel-dashlet declared with both canEdit="true" and readonly="true" now lets users edit its records, as the canEdit attribute is meant to override the readonly state.

  • Fix scheduled jobs missing for some tenants at startup

    Details

    In multi-tenancy mode, the schedules of all tenants were loaded in parallel into a non-thread-safe map. Some tenants' schedules could be lost, and their jobs were not scheduled.

  • Fix audit log processing blocked by a failing record

    Details

    When a record failed with a database error during audit log processing, the whole batch was rolled back, including its retry count. The same record then failed again on every run and blocked the other pending audit logs of its transaction.

    A failing record no longer rolls back the others, and its retry count is saved so it is discarded after the maximum number of retries.

  • Fix merging of values returned by chained actions

    Details

    When several actions were chained together, the values each of them returned were aggregated before being applied to the record, and that merge could corrupt relational values.

    New (unsaved) collection rows returned by different actions were all matched on their (missing) id and collapsed into a single row, losing data. Such rows are now matched by their client id (cid) instead, so distinct new lines stay separate.

    A many-to-one or one-to-one field pointed at a different record by a later action was deep-merged with the previous value, leaking stale fields from the old record onto the new one. The new value now fully replaces the previous one as soon as the record id changes; values are only merged when they refer to the same record.

  • Allow built-in helpers in Groovy action scripts

    Details

    Groovy <action-script> actions threw a ScriptPolicyException when using built-in scope variables ($request, $response, and transactional $em).

    ActionRequest and ActionResponse are now in the default ScriptPolicy allow list, and $em is wrapped in an allowed EntityManager delegate. JavaScript action scripts were not affected.

  • Fix live mail messages sent to the wrong tenant with Redis

    Details

    In multi-tenant setups with Redisson, live mail messages from other instances lacked tenant context and reached only default-tenant users.

    Messages now include the publisher’s tenant. Because the payload structure changed, mixed-version instances will not share live mail during rolling upgrades.

  • Fix panel-dashlet not reloading when its refresh attribute is set

  • Fix date widget portal focus handling in advanced search

    Details

    Selecting a month or year from the date picker’s dropdown on a date field in Advanced Search could freeze or crash the application.

  • Fix report images loaded from the wrong tenant

    Details

    In multi-tenancy mode, ImageTool loaded MetaFile records from the default tenant’s database for all reports. Tasks ran on ReportExecutor without tenant context and reused a default-tenant database session.

    Tasks submitted to ReportExecutor now run with the caller’s tenant and close their database session on completion.

  • Fix ContextAware tasks running on the wrong tenant

    Details

    In multi-tenancy mode, ContextAware tasks running on reused threads (such as thread pools) kept the database session opened by the first task on that thread. As the session is bound to the tenant it was opened with, later tasks ran on the first tenant’s database, whatever tenant was set.

    Tasks wrapped with ContextAware now run with their own database session, opened on the task’s tenant and closed once the task is completed. Running a ContextAware task inline for another tenant than the current database session now throws an exception, see the multi-tenancy documentation.

  • Pin EclipseLink MOXy as JAXB provider

    Details

    Call JAXBContextFactory.createContext directly instead of JAXBContext.newInstance so classpath provider lookup does not pick another implementation (such as Glassfish jaxb-runtime).

    Drop jaxb.properties files that Jakarta XML Binding 4.0 no longer reads.

  • Fix Gantt tasks collapsing to zero duration when dragged in zoomed-out views

    Details

    Dragging a task bar in the Gantt view could inadvertently set its end date equal to its start date. This occurred when a task was shorter than a single column of the current zoom level (e.g., a 4-day task in the Month view, or a 3-week task in the Year view). Because both edges of the bar snapped to the nearest column independently, they could land on the exact same date.

    The end date is now strictly derived from the snapped start date plus the task’s original duration. Moving a task now reliably preserves its length across all zoom levels.

  • Fix cross-thread database access during live mail notifications

    Details

    New mail messages were sent to the WebSocket mail channel as entities attached to the request’s database session. Building the message details on the channel’s worker thread lazy-loaded data through that session from another thread, which could fail or return incomplete details.

    The message is now reloaded on the worker thread.

  • Fix database engine detection when using a JNDI data source

  • Fix Redisson topic listeners retaining database sessions across tenants

    Details

    Redisson topic listeners run on reused threads. Database sessions opened by a listener leaked across tasks on the same thread, keeping the first message’s tenant.

    Listeners now run as ContextAware tasks with the publisher’s tenant and a dedicated database session that closes when done.

  • Fix thread leak when updating DMS permissions

    Details

    Each DMSPermissionRepository instance created its own worker thread to apply permissions to child files, and never released it. Idle threads accumulated over time.

    The worker thread is now created only when child permissions need updating, and exits once done.

  • Fix detached entity error during audit log processing

    Details

    Audit processing rebuilt a ContextHandler proxy for tracked reference fields and computed their name, which re-pointed the managed entity through a bidirectional one-to-one wiring setter and failed to cascade on flush. Read the reference label directly from the stored audit map instead, and wire the inverse side of a one-to-one on edit so the name is computed correctly at save time.

  • Fix slow recovery of pending audit logs

    Details

    The leader recovery only processed a limited number of pending transactions on each run, every few minutes, so a large backlog of pending audit logs took a long time to be recovered.

    All pending transactions are now recovered in a single run.

  • Fix time zone of API key dates

  • Fix WebSocket handlers ending a unit of work they did not start

    Details

    withAuth ignored the failure to begin a unit of work when one was already active, then ended it anyway, closing the caller’s unit of work.

    Only end the unit of work if withAuth started it.

  • Fix lock errors on audit logs processing

    Details

    When several workers processed the audit logs of the same transaction, for example the leader recovery and the asynchronous queue, PostgreSQL reported "could not obtain lock on row in relation audit_log" errors.

    Audit logs are no longer locked by rows: an advisory lock on the transaction ID ensures only one worker processes its audit logs at a time, and the other workers skip it without error.

  • Fix DMS child permissions applied before the parent permission is committed

    Details

    When sharing a folder, a background thread propagated permissions to child files before the folder permission committed. A newly created permission was not yet visible to the worker.

    Child permissions are now applied after commit, so the worker sees the saved permission.

  • Fix request scope not opened for scheduled jobs outside multi-tenancy

    Details

    Jobs run without a tenant didn’t get a request scope, so any @RequestScoped service they used failed with OutOfScopeException.

  • Fix grid row saving before validIf validation completes

    Details

    In an editable grid, pressing Enter to save a row could commit a field with a validIf condition before its validity was re-evaluated. This allowed an invalid value to be saved, or delayed showing the invalid state until a later action.

Security

  • Added sensitive JDK classes to the script policy deny list

  • Consistently exclude password fields from record data

  • Check the origin of the websocket handshake

  • Fix CORS origin matching and disable credentials by default

    Details

    With cors.allow-origin = *, a literal Access-Control-Allow-Origin: * is now sent and credentials are never allowed with *.

    cors.allow-credentials now defaults to false. Set it to true, with explicit origins, if a browser client served from another origin relies on cookies.

    cors.allow-origin is now a comma-separated list of exact origins (scheme://host[:port]) instead of a regular expression. Invalid values, such as regular expressions, are ignored with a warning: list each allowed origin explicitly. The null origin cannot be listed explicitly: it is only accepted with *, without credentials.

    This changes the CORS behavior: if cors.allow-origin is configured, check the 8.2 migration guide.

8.2.3 (2026-08-06)

Feature

  • Select field content on focus for input widgets

    Details

    Numeric (integer, long, decimal), duration, date/datetime/time, uuid, selection, and many-to-one fields now automatically select their entire content when the field receives focus — whether via Tab or a mouse click. A second click on an already-focused field places the cursor normally without forcing reselection.

    String fields are excluded, since users editing text most often just want to fix a typo rather than replace the whole value.

  • Add national phone number placeholder flag for phone widget

    Details

    Phone numbers stored without a country prefix (national format, legacy data) now display an "XX" placeholder flag — a rounded rectangle with two diagonal crossing lines — instead of the default locale country flag, making it clear that the country is unspecified.

    The flag uses theme CSS variables so it renders correctly in both light and dark mode. Selecting any country from the dropdown while in XX mode exits national format and converts the value to international format.

Change

  • Upgrade backend dependencies

    Details

    Here is the list of backend dependencies upgraded :

    • Upgrade Jackson from 2.21.4 to 2.21.5

    • Upgrade Tomcat from 10.1.55 to 10.1.57

    • Upgrade Logback from 1.5.34 to 1.5.38

    • Upgrade PostgreSQL driver from 42.7.11 to 42.7.13

  • Disable L2 cache for volatile mail entities

    Details

    MailMessage, MailFlags and MailFollower are no longer second-level cacheable. These entities are either fast-growing and reached via scattered queries (MailMessage) or hold per-user state (MailFlags, MailFollower), so the entity cache had a near-zero hit ratio while adding memory and invalidation overhead.

Fix

  • Fix one-to-many onChange firing on its own when canMove is enabled

    Details

    When a one-to-many field had canMove="true" (a valid sequence/order field), its onChange handler could fire on its own, without any user interaction with the grid, whenever the field’s value was refreshed (e.g. after a save, a version change, or any action pushing updated items back to the client) while the stored sequence values did not already match the contiguous order the widget recomputes.

    The internal reordering reconciliation now updates the sequence field without triggering onChange or marking the record dirty, consistent with the other internal writes in the widget.

  • Fix grid pagination allowing the per-page limit to be bypassed

    Details

    In a grid view, editing the page size field and decreasing it below 1 (for example to a negative value using the number input’s down arrow) was not rejected, letting the grid load records without respecting the configured maximum number of records per page. The entered page size is now validated against the configured maximum: a value above the maximum is capped to it and a non-positive value falls back to the default page size. When no maximum is configured, any positive value is allowed, so a large value can be entered to fetch all records.

  • Fix incorrect country flags in phone widget

    Details

    Corrected some country flags that were outdated or incorrect:

    • France: fixed the flag colors.

    • Martinique: replaced the obsolete flag with the current one.

    • Malaysia: fixed the number of stripes.

    • Syria: updated to the current national flag.

  • Fix Monaco Editor when redirecting from /index.html

    Details

    When deployed with a context path (e.g. /axelor-erp), the CodeEditor widget failed to load Monaco assets because location.pathname included /index.html after the auth redirect, producing a wrong base URL for the Monaco loader.

    Fixed by changing login URL from /index.html to /. As a failsafe measure in case the index.html endpoint is still used, the Monaco base URL computation now also strips /index.html from location.pathname if present.

  • Fix MetaModule data not refreshed when restoring views

  • Fix phone widget marking record dirty on focus without changes

    Details

    Focusing a phone field and immediately blurring it (without typing anything) incorrectly marked the record as dirty.

  • Compact M2M field items in action context and save requests

    Details

    When a many-to-many field held full record objects (e.g. filled by an action with records fetched from a repository), all their fields were re-sent on every subsequent save or action call. The server then attempted to update the linked records themselves, which could fail with unexpected permission errors.

    Saved many-to-many items are now sent as plain references (id, version and the display name), whether the field belongs to the model or is only defined in the view. New (unsaved) items are still sent in full so they can be created. One-to-many fields are not affected.

    The same compaction is applied when an editable grid row is committed or a record is saved from a popup editor, so many-to-many values filled by actions there no longer carry updates into later saves of the parent record.

  • Clear meta caches after database update/init

    Details

    The database update/init CLI commands now invalidate the meta caches (actions, translations, …​) once modules have been loaded. Previously, when a distributed cache was shared with a running instance, the loaded changes were not reflected until the instance was restarted.

  • Fetch missing relation fields via selector and editor popups

    Details

    When selecting a record from a selector popup or saving via an editor popup in M2O, M2M (tags), or M2M/O2M collection grid widgets, fields required for display (e.g. target-name, colorField, computed grid columns) that were not present in the popup’s grid or form view could be missing, causing values to disappear until the page was refreshed.

    Records returned from these popups now always include the fields the widget needs for display, so values no longer disappear after selecting or saving a record.

  • Fix form tab focus steal

    Details

    When a form button ran save,<action-open-in-tab> on a new record, the newly opened tab immediately lost focus and the original form tab was re-activated.

    When the view detects the active tab’s path changed, it checks if the change was caused by an internal tab-state redirect. If so, it does not re-queue a tabs.open for the same path.

  • Fix multiple line selection in grid on macOS

    Details

    Selecting multiple lines in the grid did not work on macOS because the keyboard event was mapped to the wrong modifier key. The correct key is now used so that range/multi-selection works consistently across platforms.

  • Fix validIf state being overridden by other field updates

    Details

    A field marked invalid through validIf could silently go back to looking valid, even though nothing about that field itself changed. This happened whenever another field elsewhere on the same form reacted to the same change, for example by showing or hiding itself. As a result, an invalid value could go unnoticed, and saving the record could also fail with an unexpected error.

  • Fix add sub-item button shown when canNew is false

    Details

    In a tree grid, the sub-tree header (holding the title and the "add sub-item" button) was still displayed for empty nodes even when canNew was false, letting users trigger record creation that was not permitted.

  • Fix panel header color not applied to title text

  • Fix JSON model fields not showing in selector popup grid

    Details

    When opening a selector popup for a JSON model (M2O, M2M tags, or M2M/O2M collection grid), the grid view was missing its JSON model fields, causing all custom field columns to appear empty.

    The selector popup grid now correctly resolves the JSON model, so custom fields display as expected.

  • Fix grid date search not matching single-digit month or day

    Details

    Typing a date filter with a single-digit month or day in a grid column search (e.g. 6/2026 for June 2026) produced no results. This was caused by dayjs requiring exactly two digits for MM/DD format tokens, unlike moment.js which accepted a single digit.

  • Fix phone widget country dropdown dark mode

    Details

    The country selector dropdown had hardcoded light-mode styles (white background, whitesmoke hover) that made it unusable in dark mode.

    The dropdown now uses theme CSS variables so it adapts automatically to the active theme.

8.2.2 (2026-06-18)

Feature

  • Apply default Hibernate second-level cache region settings with Redisson

    Details

    When using a Redisson-based Hibernate cache region factory, default cache region settings equivalent to the built-in Caffeine defaults are now applied: max entries and time-to-live for the entity/collection/naturalid and query regions, plus dedicated settings for the frequently-read User, Group, Role, and Permission regions. With the native region factory, only time-to-live settings are applied, as it doesn’t support max entries and max idle time.

    Any hibernate.cache.redisson.* settings defined in the application configuration take precedence over these defaults.

  • Enable L2 collection cache for auth association collections

    Details

    The auth association collections User.roles, User.permissions, Group.roles, Group.permissions and Role.permissions are now stored in the second-level collection cache (READ_WRITE). Their target entities are already cacheable, so reading these collections no longer issues a SQL query against the link table on every load.

  • Add distributed publish/subscribe topic

    Details

    DistributedFactory.getTopic(name) now provides an AxelorTopic, a publish/subscribe primitive for broadcasting messages to all application instances. Messages are delivered to every listener, including those registered on the publishing instance.

    In a distributed setup, messages are broadcast through the cache backend (e.g. Redis when using Redisson); in a single-instance setup they are dispatched in-process. The current tenant is carried with each message and restored before the listener runs.

Change

  • Use cache loader only for read-through access

    Details

    The cache loader used to be forced on get(key, mappingFunction) and the asMap() view as well, so that loading behaved consistently with the Redisson MapLoader. Now that the Redisson MapLoader is no longer used, that consistency layer is unnecessary and has been removed.

    The loader is therefore consulted only on read-through access through get(key) and getAll(keys). The get(key, mappingFunction) method and the asMap() view follow standard Caffeine/Redisson semantics and no longer invoke the loader: the mapping function is used as-is and the map view does not trigger loading.

  • Upgrade backend dependencies

    Details

    Here is the list of backend dependencies upgraded :

    • Upgrade Logback from 1.5.32 to 1.5.34

    • Upgrade Jackson from 2.21.3 to 2.21.4

    • Upgrade ByteBuddy from 1.18.8 to 1.18.10

    • Upgrade Hibernate from 6.6.50 to 6.6.53

    • Upgrade Pac4j from 6.5.1 to 6.5.3

  • Improve translation lookup performance with distributed cache providers

    Details

    Translation lookups no longer fetch the whole translation bundle from the (possibly distributed) cache on every key access. A node-local cache now fronts the shared messages cache. This can noticeably speed up translation-heavy paths when using a distributed cache provider.

    Translation changes are propagated to other nodes immediately through a distributed topic that invalidates their node-local caches, with the local cache TTL (10 minute) acting as a fallback.

Fix

  • Fix potential connection pool deadlock with Redisson cache loader

    Details

    Redisson caches no longer rely on Redisson MapLoader for read-through loading. With MapLoader, the loader runs on Redisson threads while the caller blocks on RedissonMap.get → CompletableFuture.get without timeout. A loader that accesses the database consumes an extra connection, which could deadlock when the connection pool is exhausted, for example by ParallelTransactionExecutor already consuming all connections.

    The cache loader is now relying on RMap.computeIfAbsent, which executes on the caller thread.

  • Set read-write as the default second-level cache concurrency strategy

    Details

    The default Hibernate second-level cache concurrency strategy is now explicitly set to read-write for all cache providers. Entities annotated with @Cacheable but without an explicit @Cache strategy previously fell back to the strategy reported by the region factory, which differs between providers: Caffeine/JCache defaults to read-write, but Redisson defaults to transactional.

    The transactional strategy relies on a JTA transaction manager to roll the cache back together with the database. Since the application uses resource-local transactions, a rolled-back update was never undone in the cache, leaving a stale (higher) entity version in Redis. Subsequent updates of the same record then failed with an optimistic-lock error. Forcing read-write makes the behavior consistent and correct across all providers.

  • Move to perform early

  • Scope distributed locks and atomic longs by tenant

    Details

    Distributed locks and atomic longs obtained from DistributedFactory are now scoped to the current tenant when multi-tenancy is enabled. The distributed backend (e.g. Redis) is shared across tenants, but the resources these guard (such as module initialization and meta restore) act on per-tenant databases.

  • Apply configured expiry to computed values in Redisson caches

    Details

    Values populated into a Redisson cache through get(key, mappingFunction) — including read-through get(key) and getAll(keys) loading — were stored without the configured expireAfterWrite / expireAfterAccess, so those entries never expired. The configured expiry is now applied on these paths for both loading and non-loading Redisson caches.

8.2.1 (2026-06-10)

Feature

  • Resolve current user by ID instead of code

    Details

    The current user is now resolved by its User ID (primary key) rather than its user code. Fetching by primary key is more efficient, as it can be served directly from the L2 entity cache and avoids the query cache.

    To support this, the Shiro subject principals now carry the User ID as a second principal, alongside the user code which remains the primary principal. All current-user lookups read this ID and load the user by primary key.

    Migration: backward compatibility for sessions without a User ID principal has been removed. Any pre-existing user session is rejected and the user must log in again, after which the session carries the required ID principal. This only affects deployments with sessions that persist across server restarts.

Fix

  • Fix onClick context data for line, area, scatter, radar, funnel, and gauge charts

    Details

    Line, area, scatter, radar, funnel, and gauge charts were not passing the clicked record’s data to onClick actions, so the action context was always empty. Each chart type now correctly passes the original record as context when clicked, consistent with bar and pie charts. The funnel chart type is also added to the supported chart types documentation.

  • Fix reload-dotted view param support in grid view

    Details

    Grid view now bypasses the data cache when navigating back from a form view with the reload-dotted action param set, ensuring the grid data is refreshed on the switch from form to grid even when no field has changed.

  • Fix silent failure when removing an entity referenced through a bidirectional @OneToOne

    Details

    Removing an entity could silently succeed without issuing any DELETE SQL when the target entity was on the inverse side of a bidirectional @OneToOne whose owning side declared cascade = {PERSIST, MERGE}.

    Two Hibernate behaviors combined to cause the issue :

    • The inverse @OneToOne is eagerly loaded despite FetchType.LAZY (Hibernate cannot build a proxy without a foreign-key column), so EntityManager#find pulls the owning-side entity into the persistence context.

    • During flush, the owning-side entity cascades a persist event back onto the deleted target via cascade = PERSIST. Per JPA spec, persisting a removed entity returns it to the managed state, so it cancels the deletion before any SQL is emitted.

    JpaRepository#remove now detaches the loaded inverse @OneToOne value when its back-reference is required (and therefore cannot be nulled) before delegating to JPA.remove, breaking the cascade graph that resurrected the entity. The actual DELETE reaches the database, and any foreign-key constraint is enforced normally. When the back-reference is optional, it is nulled as before so the referencing entity is preserved.

  • Preserve existing createdOn/createdBy values on insert

    Details

    On insert, AuditListener always overwrote the createdOn and createdBy fields. These fields are now populated only when they are not already set, preserving any values explicitly provided on the entity. This restores the behavior from before v8, prior to the migration from a Hibernate interceptor to an event listener.

  • Fix Caffeine L2 cache expiration configuration

    Details

    In application.conf Caffeine L2 cache configuration, durations without a unit are interpreted as milliseconds. The intended 1 hour expiration was 3.6 seconds instead.

    Expiration is now set to 10 minutes: policy.eager-expiration.after-write = 10m

    Removed default-update-timestamps-region expiration as per Hibernate recommendation: https://docs.hibernate.org/stable/orm/userguide/html_single/#caching-query-region

    Added dedicated regions for frequently-read, rarely-changed User/Group/Permission/Role, with access-based expiration (after-access = 24h) so actively-used entries stay cached while idle ones are evicted.

    Role is now cacheable="true", consistent with User/Group/Permission.

  • Fix updatedOn/updatedBy audit fields not being filled on update

    Details

    The updatedOn and updatedBy audit fields were not populated when updating records of entities mapped with @DynamicUpdate, leaving them empty in the database. They are now correctly filled on every update.

  • Allow CLI database update to load all modules, not only installed ones

    Details

    database update (without -m) previously updated only already-installed modules, silently skipping newly added ones. Now, all modules are loaded regardless of their installation status.

  • Fix phone widget paste truncating last digit when country prefix is set

    Details

    When a country was pre-filled on a phone field (e.g. France, +33), pasting a national-format number such as 0608691275 caused the last digit to be dropped. The trunk prefix (leading 0) was counted against the country’s digit mask, leaving only 9 of the 10 significant digits.

    Paste events are now intercepted and the pasted text is parsed with libphonenumber-js in the context of the selected country. If a valid number is recognized, it is converted to E.164 format before being handed to the input, bypassing the mask conflict. Pasting a full international number for a different country (e.g. +447700900123 into a French field) also correctly switches the country flag.

  • Load data init only on non-installed modules

    Details

    database update re-imported the data-init files of every installed module on each run. Data init (and demo data) are now loaded only for modules that are not yet installed.

  • Fix broken drag and drop in dashboards

  • Fix context passing for dashlet scope

    Details

    Form view context should be merged with action-view context of dashlet for domain action, relative view fetching and search of dashlet. For dashlet actions (such as toolbar/menubar actions), form view context should pass _parent and action-view context of dashlet will be passed in top level domain context.

Security

  • Restrict nested references on save

8.2.0 (2026-05-20)

Feature

  • Add zoomThreshold config option to charts

    Details

    A new zoomThreshold per-chart config option controls data zoom slider behavior. Defaults to 50.

    • > 0 — zoom slider enabled only when the number of data points exceeds the value; initial view shows the first N items.

    • = 0 — zoom slider always shown, fully zoomed out (all data visible); useful when you want precise zoom controls regardless of series size.

    • < 0 — zoom disabled; no zoom controls are shown.

    <!-- Enable zoom slider only when more than 20 data points are present -->
    <config name="zoomThreshold" value="20" />
    
    <!-- Always show zoom controls, full data visible -->
    <config name="zoomThreshold" value="0" />
    
    <!-- Disable zoom controls entirely -->
    <config name="zoomThreshold" value="-1" />
  • Set number input text alignment to right in editable grid

    Details

    Number values are now right-aligned in editable grid cells. Increment/decrement controls, which remain always visible in editable grid cells while normal form fields keep their existing hover behavior.

  • Allow extending the implements attribute when overriding an entity

    Details

    When an entity is overridden in another module, the implements attribute is now merged with the parent entity’s value. New interfaces declared in the override are added to the existing list.

    Note: this is additive only — interfaces declared on the parent entity cannot be removed by an override.

  • Add custom model support for all view types

    Details

    Custom models (based on MetaJsonRecord) can now be used across all view types: kanban, calendar, tree, gantt, cards, grid, and form.

    Custom JSON fields can be used in view-level attributes such as columnBy, sequenceBy, eventStart, eventStop, colorBy, taskStart, taskParent, parent, etc. using dotted notation (e.g. attrs.status).

    Tree view nodes now support the x-json-model attribute to filter records by their custom model. Cards and kanban views also support x-json-model in the XML schema.

  • Add jsonModel support for custom model views

    Details

    Add jsonModel field to MetaView and MetaViewCustom to properly resolve custom model views across relation widgets and view metadata APIs.

    Fixes #1650

  • Upgrade GraalJS to 25.0.3 and optimize JavaScript integration

    Details
    • Upgrade GraalJS from 24.2.2 to 25.0.3

    • Add shared engine and source caching for improved performance

    • Implement AutoCloseable on JavaScriptScriptHelper for proper context cleanup

    • Replace allowAllAccess(true) with explicit permissions for better security

    • Set ECMAScript version to 2024 (stable release)

  • Add Load more option for tree view child nodes

    Details

    Tree view child nodes are now loaded in pages using the default page size instead of all at once. When more children exist, a "Load more" row appears at the end of the list. Previously loaded children are cached so re-expanding a node does not trigger additional server calls. Refresh and sort clear the cache.

  • Make CodeEditor lighter, more usable and add auto-size / lite modes

    Details

    The CodeEditor widget was too heavy for typical form usage. It has been streamlined to be simpler, more usable, and more compact: minimap, sticky scroll, auto-completion and suggestions are disabled, scroll decoration shadow is removed, and consistent top/bottom padding is applied.

    Default height has been lowered from 400px to 360px.

    New features:

    • Auto-size mode via x-auto-size="true": editor grows with content up to the specified height (defaults to 360px, min 36px). Vertical scrollbar is hidden while content fits and shown only when overflowing.

    • Lite mode via x-lite="true": minimal editor with no line numbers, folding, hover, suggestions, context menu or current-line highlight — suitable for small inline fields.

    • placeholder attribute is now forwarded to the editor.

  • Add Export as PNG to chart dashlet menu

    Details

    Charts can now be exported as a PNG image from the dashlet action menu.

    Note: the existing export action has been renamed from "Export" to "Export as CSV" for clarity.

  • Add maxSeries config option to charts to prevent excessive memory consumption

    Details

    Chart rendering for large datasets now limits excessive series output to prevent high memory consumption. The new maxSeries per-chart config keeps only the top N series by total value. Pie, donut, radar, line, area, and scatter charts aggregate the remaining series into an "Others" bucket; bar and horizontal bar charts drop tail series to preserve scale accuracy. Defaults to 200; set to 0 to disable the limit.

    <!-- Show only the top 20 products -->
    <config name="maxSeries" value="20" />
  • Add cascade handling for json custom field relations

    Details

    Implement cascade persist, merge, and delete for custom field relationships stored as JSON (MetaJsonField). Cascade decisions are driven by a central policy: json-one-to-many to MetaJsonRecord is owned (persist/merge/remove + orphan removal), everything else is persist/merge only with no target deletion.

  • Add active session management for users

    Details

    Users can now view and revoke their active sessions from the user form. Each session displays the browser and OS, the client IP address, the sign-in time, and the last activity time. The current session is highlighted and cannot be revoked.

    Admins can view and revoke sessions for any user. Non-admin users can only manage their own sessions.

  • Add isDelegated option for custom models

    Details

    Add isDelegated boolean field to MetaJsonModel to allow custom models to use manually-defined views instead of auto-generated ones.

  • Add UUID field type support for domain entities

    Details

    Added support for UUID field type in domain entities to store uuid values more efficiently. Also added a common UuidUtils to generate UUID v4 and v7.

Change

  • Change password reset token max-age unit from hours to minutes

    Details

    The application.reset-password.max-age configuration property now accepts a value in minutes instead of hours. The default has also been reduced from 24 hours to 30 minutes. If you have customized this setting, update your configuration accordingly.

  • Change encrypted fields hashing algorithm

    Details

    The legacy $AES$ encryptor used PBKDF2WithHmacSHA1, which is no longer considered secure for password-based key derivation. Encrypted fields now use PBKDF2WithHmacSHA256 with 600,000 iterations. The ciphertext format is now self-describing: the iteration count, salt size, and IV size are embedded directly in the payload header. Decryption is therefore independent of current defaults, and future parameter changes never break existing stored values.

    The application transparently decrypts legacy $AES$ ciphertext alongside the new formats, so existing encrypted values continue to be readable without any immediate migration.

    To migrate existing $AES$ values to the new format, run the database encryption CLI tool. No encryption.old-password or encryption.old-algorithm settings are required when coming from the legacy encryptor.

    The new PBKDF2WithHmacSHA256 algorithm is significantly more CPU-intensive than the legacy PBKDF2WithHmacSHA1. Each encrypted field uses a distinct random salt, so a separate AES key must be derived per field, and that derivation dominates the processing time.

    To keep throughput acceptable on large datasets, database encryption CLI tool uses default 100,000 iterations during migration instead of the runtime default of 600,000. This can be overridden with the --iteration flag : axelor database encrypt --iteration 200000. Values written during migration can be re-migrated to a higher count at any time thanks to the self-describing format.

  • Improve related messages endpoint performance

    Details

    The messages endpoint loaded the full related entity just to read its id and class. It now uses a lightweight existence check followed by a lazy reference (proxy), avoiding the full row fetch.

  • Change Query Selector values() row shape for many-to-one fields

    Details

    As a side-effect of dropping the redundant full-entity fetch for many-to-one fields in Selector, the raw row shape returned by Selector.values(int, int) has changed for any select() that includes an m2o field.

    Before, a single slot in each row held the fully materialized referenced entity (e.g. a Title instance), followed by three scalar slots for id, version and the name field. Now, only the three scalars are emitted — the full-entity slot is gone.

    For example, with select("firstName", "title", "title.code") the row layout is now:

    [ id, version, firstName, title.id, title.version, title.name, title.code ]

    whereas previously it was:

    [ id, version, firstName, <Title entity>, title.id, title.version, title.name, title.code ]

    Callers that iterate values() by index and expected the m2o entity at a specific position must be updated to read the three scalars instead. Callers that only use Selector.fetch(int, int) are unaffected — the output map shape is unchanged ({id, $version, nameField} compact map under the reference key).

  • Rework user password update flow with identity verification

    Details

    The password update flow now requires identity verification before allowing changes. A new "Change Password" button replaces the inline password fields on the user form.

    The password field on User is no longer required — external provider users (SSO, OAuth) no longer receive a random UUID password.

    Non-admin users are now blocked from modifying restricted fields (code, group, roles, permissions, password, etc.) via the User CRUD endpoint.

    SQL migration script:

    ALTER TABLE auth_user ALTER COLUMN password DROP NOT NULL;
  • Improve message followers endpoints performance

    Details

    The message followers endpoints loaded the full target entity just to read its id and class. They now use a lightweight existence check followed by a lazy reference (proxy), avoiding the full row fetch.

  • Exclude dotted collection paths from Query Selector

    Details

    Query.Selector no longer includes dotted field paths that walk through or resolve to a collection (@OneToMany, @ManyToMany). These paths were silently accepted before and produced a SELECT column with an implicit join, which was rarely the intended behavior and often led to ambiguous or duplicated results.

    The following inputs to select(...) are now silently dropped:

    • paths where any intermediate segment is a collection, e.g. addresses.street or contact.addresses.street

    • paths whose leaf is itself a collection reached through a dotted expression, e.g. contact.addresses

    Single-segment collection fields like select("addresses") are unaffected — they keep being resolved and returned as a list of compact maps in the output.

    If your code relied on the previous behavior to flatten a sub-field of a collection into the row, switch to either selecting the collection itself (select("addresses")) and reading the sub-field from the nested compact maps, or issuing a dedicated query on the target entity.

  • Remove MySQL and Oracle database support

    Details

    Both databases had only partial support that was never actively used or maintained. Removing them to reduce complexity and focus on PostgreSQL as the primary supported database.

  • Speed up embedded Tomcat startup with jar scan filtering

    Details

    Embedded Tomcat was scanning every jar on the classpath for annotations, TLDs, and web fragments. Configure StandardJarScanFilter to skip all jars by default. Add --tld-scan-jars / --pluggability-scan-jars CLI options to configure TLD scanning (JSP taglibs) and Servlet 3.0 pluggability scanning (web-fragment.xml, @WebServlet / @WebFilter / @WebListener)

  • Upgrade backend dependencies

    Details

    Here is the list of backend dependencies upgraded :

    • Upgrade Jackson from 2.21.0 to 2.21.3

    • Upgrade Jsoup from 1.22.1 to 1.22.2

    • Upgrade Swagger from 2.2.43 to 2.2.50

    • Upgrade ByteBuddy from 1.18.5 to 1.18.8

    • Upgrade Birt from 4.22.0 to 4.23.0

    • Upgrade Snakeyaml from 2.5 to 2.6

    • Upgrade Ldaptive from 2.4.2 to 2.5.0

    • Upgrade Hibernate from 6.6.42 to 6.6.50

    • Upgrade Redisson from 3.52.0 to 4.3.1

    • Upgrade Junit from 5.14.3 to 6.0.3

    • Upgrade Groovy from 4.0.30 to 4.0.32

    • Upgrade Tomcat from 10.1.52 to 10.1.55

    • Upgrade Slf4j from 2.0.17 to 2.0.18

    • Upgrade Undertow from 2.3.23 to 2.3.24

    • Upgrade Gradle from 8.14.3 to 8.14.5

    • Upgrade PostgreSQL JDBC from 42.7.10 to 42.7.11

    • Upgrade Caffeine from 3.2.3 to 3.2.4

    • Upgrade Resteasy from 6.2.15 to 6.2.16

  • Improve chart rendering for large datasets

    Details

    Chart rendering has been improved to handle large datasets more reliably.

    • A loading spinner is shown while chart data is being fetched.

    • Axis labels are truncated when space is limited and a tooltip reveals the full label on hover.

    • Large mode and LTTB downsampling are applied automatically on scatter and line charts with very large datasets to keep rendering fast.

  • Improve user password requirements

    Details

    The password policy system has been reworked to provide more useful, secure, and customizable built-in policies.

    Default behavior changes:

    • Minimum password length increased from 4 to 8 characters (mandatory, cannot be disabled).

    • New password must differ from the current one (mandatory, cannot be disabled).

    • Passwords must not contain the user login code (case-insensitive substring check).

    New opt-in policies — each can be enabled with a single configuration property (user.password.<policy>.enabled = true):

    • digits — requires a minimum number of digit characters.

    • lowerCase — requires a minimum number of lowercase characters.

    • upperCase — requires a minimum number of uppercase characters.

    • specialChars — requires a minimum number of special (non-alphanumeric) characters.

    • pattern — requires the password to match a custom regular expression.

    • score — requires a minimum strength score computed (0–4 scale).

    Breaking changes:

    • The user.password.pattern property has been removed. Use the new pattern policy (user.password.pattern.enabled = true / user.password.pattern.value = <regex>) or combine the new built-in policies to achieve equivalent constraints.

    • The user.password.pattern-title property has been removed.

  • Improve RestService download performance

    Details

    The download endpoint loaded the full entity to read a single field, which triggered an unnecessary fetch of the entire row including its associations. The field is now retrieved with a targeted projection query, reducing latency and avoiding spikes on entities with heavy relations. The permission check has also been consolidated.

  • Move CacheBuilder#weakValues/weakKeys to CaffeineCacheBuilder

Fix

  • Avoid shutdown hook deadlock during Tomcat startup

    Details

    The shutdown hook is now registered after Tomcat has fully started instead of during initialization, preventing a potential deadlock if shutdown is triggered while startup is still in progress.

  • Fix NPE from Mapper.toBean() with computed fields

    Details

    When mapping values to a bean, computed fields often depend on other field values. By populating non-virtual fields first, we ensure that dependencies are available when virtual fields are processed.

  • Fix reload expanded node on tree grid refresh

  • Fix encrypted fields migration

    Details

    Fix several issues during encrypted fields migration :

    • non-deterministic pagination issue that preventing already-migrated records from being re-processed at shifted offsets.

    • Handle inheritance strategies for entities : skip inherited fields for SINGLE_TABLE and `JOINE`D so they are migrated by the parent class.

    Also few improvements has been done :

    • Use JDBC batch statements instead of individual JPQL UPDATE queries : select decrypted values via JPA (so the old encryptor is applied automatically), then re-encrypt and persist via PreparedStatement.addBatch() / executeBatch() for better performance and reduced DB load

    • Add validation of migration settings (required password, known algorithms, warn on no-op rotations) before starting

    • Infer missing old encryption settings from current values when rotating : only need to configure what actually changed. Ex : no need to set old-password is we rotate only the algorithm.

  • Fix tree node orderBy attribute not being applied

    Details

    orderBy attribute defined on tree node was not being applied during the search request of the node.

  • Fix itemSpan not applied to custom JSON fields in form view

    Details

    itemSpan defined on parent container was not being applied to custom JSON fields in form view.

  • Fix translatable text/html widgets ignoring translation in readonly

    Details

    When a translation exists for the current locale, the text and html widgets now display the translated value in readonly mode, matching the behavior of the string widget. Previously the editable input was shown with the raw stored value instead.

  • Fix Tags widget readonly rendering in form and grid views

    Details

    When a Tags widget (widget="Tags") was set to readonly, it rendered as a plain empty text input instead of displaying tag pills. The readonly Tags widget now renders tag pills directly using the same Overflow layout as the grid cell renderer, with proper overflow menu support.

  • Fix copy and export errors not displayed to user

    Details

    When an exception is thrown from the copy or export methods, the error is now displayed in a dialog instead of being silently ignored.

  • Fix view-param limit support in tree view

    Details

    The view-param limit support in tree view was not being applied to the root node.

  • Fix scrollbar flash when downloading file

  • Fix translatable text/html widgets flag button position and visibility

    Details

    In the text widget, the translation flag button was not positioned correctly next to the input. In the html widget, the flag button was hidden entirely when a translation existed. Both widgets now show the flag button at the top-right of the field, consistent with the string widget behavior.

  • Avoid full m2o entity fetch in Query Selector

    Details

    When a many-to-one field like title was included in a select(), the generated JPQL contained both the bare join alias (_title) and its id/version/nameField scalars. The bare alias caused Hibernate to materialize the entire referenced entity — every column of the target table — even though only the compact {id, $version, nameField} map was ever returned to callers.

    The full entity fetch is now dropped: only the three scalars are selected, which is all fetch() actually consumes.

  • Consolidate form validation errors by priority per field

    Details

    When saving a form with multiple constraint violations on the same field, all errors were shown (e.g. "Login is invalid" and "Login is too small"). Now only the highest-priority error per field is displayed, using this order: required > min/max > pattern > custom errors > invalid.

    Also fix server-side validation errors using the raw field name (e.g. "code") instead of the field title (e.g. "Login").

  • Fix XSS vulnerability in download filename handling

    Details

    Use textContent instead of innerHTML when setting the download link text to prevent script execution via crafted filenames.

  • Fix NPE in JobController cron validation with no further valid firing times

  • Fix central logout and session timeout redirect when MFA is enabled

    Details

    MfaClient restores pending profile but overwrites its clientName. Now always restore pending client name when session is marked as fully authenticated by MFA authenticator.

  • Fix mail followers recipients request limit

    Details

    The mail followers recipient request limit wasn’t provided, allowing related service to be called with an unbounded limit. Cap the effective limit to api.pagination.max-per-page so that the email search respects the configured pagination maximum and use api.pagination.default-per-page as the default when no limit is specified in the request.

  • Fix pass selected in custom collection field context

  • Fix ConcurrentModificationException in AuditTracker on entity delete

    Details

    AuditTracker.processDelete() now iterates over a snapshot of the deleted entities set, preventing ConcurrentModificationException when cascaded deletes add new entries during iteration.

  • Fix various cache issues

    Details

    Fix various issues with caches including incorrect log arguments, swallowed exceptions in eviction listeners, NPE in default getAll() method, premature eviction of tenant caches in cluster environments, closing underlying tenant-specific caches when closing wrapper tenant-aware cache.

  • Fix NPE in AuditProcessor when processing computed fields

  • Fix theme colors not applied to panel dashlet

    Details

    Refactor dashlet widget to use the Panel component from @axelor/ui so that theme color variables are correctly applied to the dashlet header and body. Also fix chart views not occupying the full height of the dashlet container.

  • Fix handle focus navigation in grid view search column

  • Fix gauge chart showing NaN% when category axis is formatted

    Details

    Gauge charts were reading the value from the category axis field (e.g. month), which gets formatted into a string (e.g. "May") by the chart data pipeline. Using a string as the gauge value caused ECharts to display NaN%. The gauge now correctly reads the value from the series key field instead.

  • Fix image widget binary field to use $upload

    Details

    Image widget on binary fields now uses the $upload mechanism via the upload endpoint instead of sending the file as a base64 data URL in the save payload.

  • Fix NPE in MetaFiles.upload() recovery block

    Details

    Add null check for temporary file copy to prevent masking of root persistence exceptions with NPEs during new file uploads.

  • Fix calendar drag-and-drop not saving when popover is open

    Details

    Close the event popover when a drag operation starts on the calendar view. Previously, if the template popover was open, dragging an event to another date would not trigger a save.

  • Fix OOM in OpenAPI scanner by limiting scan to Axelor modules

    Details

    OpenAPI scanner now limits the scan to Axelor modules only, reducing memory usage and preventing OutOfMemoryError. So resources in non-Axelor JARs no longer appear in the spec.

  • Fix date widget portal focus handling in editable grid

    Details

    Keep the date picker rendered in portal mode for grid layout stability, while preventing focus-trap and blur interactions from causing repeated blur loops or React update crashes when using month/year selectors.

  • Fix cache busting of JSP and HTML files

    Details

    Exclude .jsp, .html, and .htm files from static resource cache busting in NoCacheFilter. These files should not receive immutable cache headers as they are server-rendered or serve as application entry points.

Security

  • Upgrade Pac4j & Shiro dependencies

    Details

    Upgrade Pac4j from 6.3.1 to 6.5.1 and Shiro from 2.0.6 to 2.1.0 for security reasons

  • Fix RestService upload missing file content type validation

    Details

    Only the declared file type from the request data was validated against the whitelist/blacklist, allowing a malicious user to bypass validation by sending a file with an allowed extension but different content. Now the actual file content is inspected via Apache Tika for both attachment and non-attachment uploads.

  • Fix file upload content type validation to prevent extension spoofing

    Details

    MetaFiles.checkType(File) now pass null as the filename hint to Tika, ensuring detection relies solely on magic bytes. Previously, the filename was used as a hint and could influence the result, allowing a malicious file disguised with an allowed extension to bypass validation.