---
title: 3.9 Release Notes
description: "Warning Elements 3.9 is still under active development (current version: `3.9.0-SNAPSHOT`) and has not been released. The contents of this page are a draft and may change before the final release. ## Overview Elements 3.9 adds per-[Application](https://namazustudios.com/glossary/application/) [Profile](https://namazustudios.com/glossary/profile/) limits and…"
url: "https://namazustudios.com/docs/releases/3-9-release-notes/"
updated: "2026-09-23"
category: Releases
---

# 3.9 Release Notes

Warning

Elements 3.9 is still under active development (current version: `3.9.0-SNAPSHOT`) and has not been released. The contents of this page are a draft and may change before the final release.

## Overview

Elements 3.9 adds per-[Application](https://namazustudios.com/glossary/application/) [Profile](https://namazustudios.com/glossary/profile/) limits and automatic primary-[Profile](https://namazustudios.com/glossary/profile/) creation, a new way to attach a [User](https://namazustudios.com/glossary/user/)'s [Profile](https://namazustudios.com/glossary/profile/) to a [Session](https://namazustudios.com/glossary/session/) by naming an [Application](https://namazustudios.com/glossary/application/) instead of an explicit [Profile](https://namazustudios.com/glossary/profile/), and account linking for the [OIDC](https://namazustudios.com/glossary/oidc/) browser-redirect login flow.

## Highlights

- **Per-[Application](https://namazustudios.com/glossary/application/) [Profile](https://namazustudios.com/glossary/profile/) limits** — a new `maxProfiles` setting on [Application](applications) bounds how many profiles a [User](https://namazustudios.com/glossary/user/) may create for it.
- **Automatic primary [Profile](https://namazustudios.com/glossary/profile/) creation** — a new `autoCreateProfile` setting on [Application](https://namazustudios.com/glossary/application/), combined with a new `autoCreateProfileApplicationNameOrId` field on the [User](https://namazustudios.com/glossary/user/)-create/signup request, lets Elements create a [User](https://namazustudios.com/glossary/user/)'s primary [Profile](https://namazustudios.com/glossary/profile/) for an [Application](https://namazustudios.com/glossary/application/) automatically at signup time. See [Creating a User](creating-a-user).
- **[Session](https://namazustudios.com/glossary/session/) creation by [Application](https://namazustudios.com/glossary/application/), including auto-create** — username/password, [OAuth2](https://namazustudios.com/glossary/oauth2/), and [OIDC](https://namazustudios.com/glossary/oidc/) [Session](https://namazustudios.com/glossary/session/) requests can now pass `applicationNameOrId` to attach the [User](https://namazustudios.com/glossary/user/)'s primary [Profile](https://namazustudios.com/glossary/profile/) for that [Application](https://namazustudios.com/glossary/application/), instead of an explicit `profileId`/`profileSelector`. If no primary [Profile](https://namazustudios.com/glossary/profile/) exists yet, it's now created automatically, subject to the [Application](https://namazustudios.com/glossary/application/)'s own `autoCreateProfile`/`maxProfiles` settings. See [Sessions](sessions).
- **[authoritative](https://namazustudios.com/glossary/authoritative/) [Profile](https://namazustudios.com/glossary/profile/) pictures and display-[name](https://namazustudios.com/glossary/name/) validation** — new `authoritativeProfilePicture` and `displayNameRegex` settings on [Application](https://namazustudios.com/glossary/application/). See [Applications](applications).
- **Account linking via the [OIDC](https://namazustudios.com/glossary/oidc/) browser-redirect flow** — starting a login attempt while already holding a [Session](https://namazustudios.com/glossary/session/) now links the resulting external identity to that [User](https://namazustudios.com/glossary/user/) instead of creating a new one, with a new `confirmToken`-gated confirmation step to keep the mutation off the unauthenticated provider callback. See [OIDC Login for Thick Clients](oidc-login-for-thick-clients-browser-redirect-flow).
- **Progress API fixes and a new advance-progress endpoint** — `POST /progress` and the superuser `PUT /progress/{id}` path are fixed, and a new `POST /progress/{progressId}/advance` endpoint lets a Mission opt in to client-driven progress advancement. Reported, diagnosed, and prototyped by community contributor [@hobolabsdigital](https://github.com/hobolabsdigital) -- thank you!
- **System-wide password policy** — a new deploy-time regex config gates every password a client submits (signup, password reset, admin-set password, account linking), paired with a human-readable description shown in the validation error. See below.
- **Stale Datastore/Mapper fix on [Element](https://namazustudios.com/glossary/element/) (re)deploy** — a singleton that captured Elements' shared Mongo `Datastore` could go stale on the next [Element](https://namazustudios.com/glossary/element/) (re)deploy; see below.
- **CRUD events across nearly every [DAO](https://namazustudios.com/glossary/dao/)** — create/update/delete events, previously only produced by a handful of DAOs, now cover nearly all of them, making it practical to build audit logging and similar cross-cutting Elements. See [Events](events).
- **Opt-in [Guice](https://namazustudios.com/glossary/guice/) PRODUCTION stage for injectors** — every [Guice](https://namazustudios.com/glossary/guice/) injector in the platform (per-[Element](https://namazustudios.com/glossary/element/), jetty-ws itself, the `migrate`/`setup` tools, and more) can now be built with [Guice](https://namazustudios.com/glossary/guice/)'s `Stage.PRODUCTION` instead of the default `Stage.DEVELOPMENT`, via a new system property/environment variable. This pairs with the Datastore/Mapper fix above: capturing the shared `Datastore` in an eager singleton is safe now, so there's no new risk from the earlier construction timing under `PRODUCTION`. See below.

## New Features

### Per-[Application](https://namazustudios.com/glossary/application/) [Profile](https://namazustudios.com/glossary/profile/) Limits and Auto-Create

`Application` gains two new fields: `maxProfiles` (defaults to `1`) caps how many profiles a [User](https://namazustudios.com/glossary/user/) may create for that [Application](https://namazustudios.com/glossary/application/), and `autoCreateProfile` (defaults to `true`) governs whether a [User](https://namazustudios.com/glossary/user/)'s primary [Profile](https://namazustudios.com/glossary/profile/) is created automatically when requested via `autoCreateProfileApplicationNameOrId` on [User](https://namazustudios.com/glossary/user/) creation. Lowering `maxProfiles` never affects profiles that already exist -- only new [Profile](https://namazustudios.com/glossary/profile/) creations are gated. Existing applications with no value set for these fields behave as if they were set to the defaults.

### [Session](https://namazustudios.com/glossary/session/) Creation by [Application](https://namazustudios.com/glossary/application/)

Username/password, [OAuth2](https://namazustudios.com/glossary/oauth2/), and [OIDC](https://namazustudios.com/glossary/oidc/) [Session](https://namazustudios.com/glossary/session/) requests accept a new `applicationNameOrId` field (an [Application](https://namazustudios.com/glossary/application/) [name](https://namazustudios.com/glossary/name/) or [id](https://namazustudios.com/glossary/id/)). If neither `profileId` nor `profileSelector` is specified, Elements resolves the [User](https://namazustudios.com/glossary/user/)'s primary [Profile](https://namazustudios.com/glossary/profile/) for that [Application](https://namazustudios.com/glossary/application/) and attaches it to the [Session](https://namazustudios.com/glossary/session/). If no primary [Profile](https://namazustudios.com/glossary/profile/) exists yet, one is now created automatically, subject to the same `autoCreateProfile`/`maxProfiles` gating as signup-time auto-create, via the same `ProfileDao#createSlottedProfile` path; if the [Application](https://namazustudios.com/glossary/application/) can't be resolved, or auto-create isn't configured for it, the [Session](https://namazustudios.com/glossary/session/) is simply created without a [Profile](https://namazustudios.com/glossary/profile/).

### Account Linking via the [OIDC](https://namazustudios.com/glossary/oidc/) Browser-Redirect Flow

Starting an [OIDC](https://namazustudios.com/glossary/oidc/) browser-redirect login attempt (`POST /OIDC/Session`) while already holding a [Session](https://namazustudios.com/glossary/session/) now links the resulting external identity to that [User](https://namazustudios.com/glossary/user/), the same way the existing [Account Linking](account-linking) endpoints do for a possessed `id_token`. No new request field is involved; whether an attempt links or creates a new [User](https://namazustudios.com/glossary/user/) is decided purely by whether the caller had a [Session](https://namazustudios.com/glossary/session/) when the attempt was started.

Because the provider's callback that validates the external identity is always an unauthenticated redirect from the identity provider, with no way to confirm it's the same caller that started the attempt, it no longer performs the account-link mutation itself. A new `confirmToken`, returned only in the original `begin()` response, gates a new `POST /OIDC/Session/{id}/confirm` step that performs it. See [OIDC Login for Thick Clients](oidc-login-for-thick-clients-browser-redirect-flow) for the full sequence. This closes a case where a leaked `state` value, which (unlike the poll `id`) necessarily passes through the browser and the identity provider, could otherwise have let an attacker permanently link their own external identity to a victim's account.

### [authoritative](https://namazustudios.com/glossary/authoritative/) [Profile](https://namazustudios.com/glossary/profile/) Pictures and Display-[name](https://namazustudios.com/glossary/name/) Validation

`Application` gains two more new fields: `authoritativeProfilePicture` (defaults to `false`), which when `true` blocks a [User](https://namazustudios.com/glossary/user/) from editing their own [Profile](https://namazustudios.com/glossary/profile/) picture for that [Application](https://namazustudios.com/glossary/application/) via the [REST](https://namazustudios.com/glossary/rest/) API (it must be set by backend/[Element](https://namazustudios.com/glossary/element/) code instead), and `displayNameRegex` (optional), a Java regular expression a [Profile](https://namazustudios.com/glossary/profile/)'s display [name](https://namazustudios.com/glossary/name/) must match for that [Application](https://namazustudios.com/glossary/application/) -- [Profile](https://namazustudios.com/glossary/profile/) creates/updates with a non-matching display [name](https://namazustudios.com/glossary/name/) are rejected. Leave it blank to skip the check.

### Progress API Fixes and Advance-Progress Endpoint

`POST /progress` no longer 400s with `"Profile - must not be null"` when a valid [Profile](https://namazustudios.com/glossary/profile/) is supplied, and the superuser `PUT /progress/{id}` path no longer rejects every possible request body. Both were reported with full root-cause analysis by community contributor [@hobolabsdigital](https://github.com/hobolabsdigital) in [#2](https://github.com/NamazuStudios/elements/issues/2) and [#3](https://github.com/NamazuStudios/elements/issues/3) -- thank you for the thorough repros and the `sequence`/`currentStep` data-model deep-dive.

Mission gains a new `authoritative` field (defaults to `true`). A new `POST /progress/{progressId}/advance` endpoint decrements a Progress's remaining actions, advancing Steps and issuing Rewards as needed -- superusers may always call it, and a regular [User](https://namazustudios.com/glossary/user/) may only call it for their own Progress on a Mission explicitly marked `authoritative: false`. This is the client-driven progress advancement @hobolabsdigital originally prototyped in #3, now gated per-Mission so [authoritative](https://namazustudios.com/glossary/authoritative/)-integrity is preserved by default.

### System-Wide Password Policy

Two new deploy-time configuration values, `dev.getelements.elements.password.policy.regex` and `dev.getelements.elements.password.policy.description`, let an operator require passwords to match a regular expression before Elements will accept them. The regex is enforced everywhere a password is accepted or changed by a client: signup, password reset completion, admin-set password, self-[Service](https://namazustudios.com/glossary/service/) change-password, and email/username-password account linking. It is not applied to server-generated passwords, such as the bootstrap default superuser account or mock/test accounts. See [Properties](properties) for the exact keys.

The description value is a plain-text, human-readable explanation of the requirement (e.g. "Password must be at least 4 characters."), returned as part of the error message when a submitted password fails the regex, so a client can surface it directly without parsing the regex itself. The default regex is `.{4,}` (minimum length only, matching the length of the built-in default superuser password), which is a change from Elements' previous behavior of accepting any non-blank password. Operators are responsible for keeping the regex and its description in sync -- Elements does not attempt to derive one from the other. This is system-wide only; there is no per-[Application](https://namazustudios.com/glossary/application/) or per-[Tenant](https://namazustudios.com/glossary/tenant/) override.

### [Guice](https://namazustudios.com/glossary/guice/) SPI Loading-Strategy Escape Hatch

The package-level `@GuiceOptions` annotation is now wired into `GuiceSpiModule`, giving third-party [Element](https://namazustudios.com/glossary/element/) authors an opt-in escape hatch for the `[Guice/ExposedButNotBound]` crash that can occur when an exported [Service](https://namazustudios.com/glossary/service/) has no locally-discovered implementation. Elements that don't declare `@GuiceOptions` see no behavior change -- the existing bind/expose scanning remains the default `LEGACY` strategy. Authors can instead declare `GUICE_MODULE_ONLY` to defer every exported [Service](https://namazustudios.com/glossary/service/) to their own `@GuiceElementModule`(s), or `STRICT` to fail fast at startup with a clear error naming the unbound [Service](https://namazustudios.com/glossary/service/) instead of [Guice](https://namazustudios.com/glossary/guice/)'s generic crash. See [Introduction to Guice and Jakarta in Elements](introduction-to-guice-and-jakarta-in-elements).

### CRUD Events Across Nearly Every [DAO](https://namazustudios.com/glossary/dao/)

Before 3.9, only three DAOs (`ElementDeploymentDao`, `MultiMatchDao`, and `ReceiptDao`) published create/update/delete events. That coverage now extends to nearly every [DAO](https://namazustudios.com/glossary/dao/) in the system, including profiles, sessions, applications and their configurations, auth schemes, [OIDC](https://namazustudios.com/glossary/oidc/) login attempts and provider configurations, inventory items and [Item](https://namazustudios.com/glossary/item/) ledger entries, missions and progress, schedules, leaderboards and scores, reward issuances, save data documents, large objects, followers and friends, FCM registrations, smart contracts, vaults and wallets, and more.

Each new event follows the same two-variant pattern already used by the pre-3.9 DAOs and described in [Events](events): a transactional variant carrying a `Transaction` argument, published immediately as part of the write, and a plain variant published only once the enclosing transaction commits (and dropped entirely if it rolls back). A handful of DAOs expose fewer variants where it matches the entity's actual lifecycle -- for example, `ScoreDao` fires a single `SCORE_CREATED_OR_UPDATED` event since scores are always upserted, `ItemLedgerDao` only fires a created event since ledger entries are immutable, and `FriendDao` only fires a deleted event since friendships are formed implicitly through mutual follows rather than a direct create call.

As with all [Element](https://namazustudios.com/glossary/element/) events, the [authoritative](https://namazustudios.com/glossary/authoritative/) list of event names and their argument types for a given [DAO](https://namazustudios.com/glossary/dao/) is discoverable at runtime via the CMS's Produced Events screens, or the underlying `GET /elements/system` and `GET /elements/Application` endpoints; see [Events](events) for details.

### Opt-In Eager Singleton Construction ([Guice](https://namazustudios.com/glossary/guice/) Stage.PRODUCTION)

Every [Guice](https://namazustudios.com/glossary/guice/) injector in the platform can now be built with `Stage.PRODUCTION` instead of the default `Stage.DEVELOPMENT`, controlled by the `dev.getelements.elements.Guice.stage` system property (or the `ELEMENTS_GUICE_STAGE` environment variable if the property isn't set). Leaving both unset keeps today's `DEVELOPMENT` behavior everywhere, including for [Element](https://namazustudios.com/glossary/element/) injectors -- `PRODUCTION` is strictly opt-in. A server deployment that wants the benefits below should set `dev.getelements.elements.Guice.stage=PRODUCTION` (or the equivalent environment variable) in its own launch configuration.

When enabled, two things change for [Element](https://namazustudios.com/glossary/element/) authors:

- Any [Service](https://namazustudios.com/glossary/service/) class you mark `@Singleton` is constructed at [Element](https://namazustudios.com/glossary/element/)-load time, not lazily on first use. Previously only bindings explicitly marked `.asEagerSingleton()` in a [Guice](https://namazustudios.com/glossary/guice/) module were built eagerly; a plain `@Singleton` class was left to first use.
- Every binding in your [Element](https://namazustudios.com/glossary/element/)'s injector is validated up front at load time, so a misconfigured binding fails fast when the [Element](https://namazustudios.com/glossary/element/) loads instead of surfacing later at first invocation.

This is safe to rely on together with the Datastore/Mapper fix below: injecting the shared `Datastore` into an eager singleton no longer risks capturing a stale snapshot, since the `Datastore` you receive is a stable proxy regardless of when it's constructed. If your own [Service](https://namazustudios.com/glossary/service/) binds another shared, mutable dependency directly (not through a similar proxy or a `Provider`), review whether earlier eager construction under `PRODUCTION` stage could now capture a stale reference to it.

## Bug Fixes

### Stale Datastore/Mapper State on [Element](https://namazustudios.com/glossary/element/) Redeploy

Any singleton that captured the injected Morphia `Datastore` directly could go stale the moment an [Element](https://namazustudios.com/glossary/element/) (re)deploy rebuilt and swapped the shared `Datastore`/Mapper -- most reliably on every redeploy, since an [Element](https://namazustudios.com/glossary/element/)'s eager singletons are constructed before its own entities are even registered. The injected `Datastore` is now a stable proxy that always forwards to whichever instance is live at call time, so holding a reference to it -- in Elements' own internal DAOs, or in a downstream [Element](https://namazustudios.com/glossary/element/)'s -- is safe by construction.
