Unicode research · Versioning

Unicode Versioning and Character Stability: What Can and Cannot Change

Understand Unicode 18.0 versioning, immutable code point assignments, name and normalization stability, evolving properties, library mismatches and reproducible regression tests.

Technical referenceUnicode baseline: 18.0.0

How Unicode releases evolve

Unicode is an expanding standard, not a frozen list of characters. Each release can assign additional code points, add scripts and emoji, update character-property data, revise segmentation guidance and publish corrections or clarifications. A system that handles UTF-8 correctly can still display unexpected tofu boxes, segment grapheme clusters differently, or produce different search results after its Unicode data are updated. Those outcomes reflect several independent software layers rather than instability in the numeric identity of characters.

As of October 2026, the current major release is Unicode 18.0.0, published 16 September 2026. It added 13,007 encoded characters, bringing the total to 172,808, including characters for newly encoded scripts. The release's core standard, annexes, the Unicode Character Database (UCD) and synchronized Unicode technical standards form the versioned baseline. A downstream browser or library may still use an older version, even if its operating system is current.

A version number such as 18.0.0 identifies the standard and associated data; it is not automatically the same as an ICU version number, a CLDR version, a browser release, a font revision, or an emoji font's repertoire. Many practical incompatibilities come from version skew between these components.

Guarantees: identity, positions, names and normalization

The Unicode Consortium publishes explicit Character Encoding Stability Policies, which protect encoded text as new repertoire is added. These policies matter because storage systems and protocols must continue to interpret existing code points consistently across decades.

PolicyMeaningBoundary of the guarantee
Encoding stabilityOnce a character has been encoded, it is not moved, removed or reassigned to a different character.A code point can later be deprecated without being deleted.
Name stabilityThe formal Unicode Name of a previously encoded, non-reserved code point stays stable.Name aliases and annotations may supplement historical names or correct their interpretation.
Formal name-alias stabilityOnce an established formal alias is assigned, it will not be changed or removed.Not every informal colloquial name is a formal alias.
Normalization stabilityFor strings using characters already assigned in the compared releases, normalization results remain stable under the stated policy.Newly assigned characters unknown to older software are outside that premise.
Decomposition and combining-class stabilityA character's decomposition mapping and canonical combining class do not change after assignment under the relevant policy.These guarantees do not freeze all properties or all rendering behavior.
Identity stabilityProperty updates must not change a character's fundamental identity.The Consortium may refine other properties where needed.

These rules distinguish numeric stability from display stability. The fact that U+1F600 is always GRINNING FACE does not require every font to draw the same mouth, eye shapes or colorful rendering. Nor does it guarantee every OS can display that code point when it was newly introduced.

Strong normalization stability, applicable since Unicode 4.1, includes the condition that the input string's characters were assigned in both compared versions. Do not generalize it to arbitrary strings containing unassigned code points from the older implementation: the older implementation might leave a future combining character untouched because it does not know its new properties.

What can still change between Unicode versions

Some properties are deliberately allowed to evolve within specified policy constraints. Updates can change category assignments for previously unassigned code points, add or refine script associations, adjust derived properties, revise rules for grapheme/word/sentence segmentation, and add emoji sequences or rendering recommendations. New code points can affect how neighboring code points are grouped or how a text run is processed.

  • Previously unassigned code points: Once Unicode assigns them, their General_Category moves away from Cn to the assigned class, and their other properties become specified.
  • Segmentation: UAX #29 rules and data can be updated. Old and new environments can disagree about a grapheme boundary without disagreeing about the encoded text.
  • Identifier restrictions: Security profiles and confusable data evolve; an application that validates identifiers must document and test its chosen version.
  • Collation: Ordering strings is not the same as ordering code points. UTS #10, locale data and tailoring can change without renumbering characters.
  • Emoji support: An OS's pictographic repertoire depends on fonts and shaping technology as well as Unicode/emoji property data.
  • Case and script processing: Rules and mappings are versioned; stable character identity does not imply all locale-sensitive mappings are permanently invariant.

Rendering differences are sometimes caused by fonts rather than Unicode rules. Updating a browser's font fallback list can change appearance while the selected UCD version remains identical. Conversely, ICU can update UAX #29 segmentation data while the fonts remain unchanged.

Versioned UCD data, runtime support and fallback

The UCD is a collection of versioned text files that include UnicodeData.txt, DerivedCoreProperties.txt, Scripts.txt, ScriptExtensions.txt, property aliases, normalization data and segmentation property tables. A robust implementation reads a self-consistent release set. Mixing a current Scripts.txt with an obsolete PropertyValueAliases.txt can fail to interpret newly added script identifiers; pairing mismatched derived properties and break rules may create a segmentation bug.

For reproducible processing, store or log both the application/library version and the Unicode data version. Python exposes the bundled release through unicodedata.unidata_version. ICU exposes its own library and Unicode data versions through its APIs. Browser JavaScript generally does not offer a single standard API returning the UCD version of the entire engine; recording the browser/engine version and comparing observed behavior against versioned test vectors is useful.

When encountering a future scalar value that is unknown to your current UCD, an interchange encoder should still be able to preserve a well-formed UTF-8/UTF-16 sequence. Rendering may fall back to a missing-glyph box, and property lookup may report an unassigned/default value. It is usually safer to preserve unknown scalar values rather than replace them merely because the local character database is old.

Worked examples of cross-version behavior

Stable assigned code point, unstable glyph appearance

The numeric scalar U+1F600 stays U+1F600 in all future releases. A monochrome font, a colored emoji font and a missing-font fallback can represent it with different glyphs or no supported glyph. A UTF-8 byte comparison can stay identical while screenshots differ.

Older runtime sees a newly assigned character as unassigned

Suppose Unicode version V+1 adds a character at a code point that was unassigned in version V. Both runtimes may decode its UTF-8 bytes as a valid scalar, but version V's character database can still classify it as Cn. A validator that rejects all Cn values will reject newer but legitimate text. A font that lacks the glyph will similarly display a fallback shape. This behavior does not mean the encoding is malformed.

Normalization guarantee with an important condition

For the precomposed U+00E9 and the decomposed sequence U+0065 U+0301, NFC/NFD equivalence is covered by normalization stability in versions that already assign those characters. If the text instead contains a new combining character not assigned in an older version, the older normalization implementation might not achieve the newer version's normalized form. This condition matters for cross-system equality tests and cached normalized keys.

Legacy names may appear odd

Unicode keeps established formal character names stable even when community terminology changes or a historical name is imperfect. Developers should not silently rename code points in a character table; use official NameAliases or documentation notes for improved discoverability.

Implementation architecture and compatibility strategy

Separate character storage, data-property lookup, higher-level algorithms and rendering. Storage and transport should validate well-formed encoding rather than require that every scalar value be assigned in the runtime's current database. A property-based algorithm should state which UCD release it uses. Rendering should degrade gracefully when fonts lack a newly assigned character while keeping the original logical string available for copy/paste.

Useful practices include pinning the Unicode version of data-generation jobs, accepting well-formed unknown scalar values at the interchange layer, recording transformations in search/index pipelines, and maintaining compatibility tests for clients with known older releases. A migration can update a search index's derived keys without rewriting original user text. If identifiers depend on Unicode profiles, version updates should be reviewed as policy changes rather than assumed harmless dependency upgrades.

For web apps, distinguish a new Unicode code point from a newly available CSS font or OS emoji glyph. The former is part of the data standard; the latter is a device capability. There is no reliable general assertion that “my app supports Unicode 18” based solely on being able to decode UTF-8.

Practical tests in Python and JavaScript

# Python: record UCD version and validate old assignments
import sys
import unicodedata as ud

print("Python:", sys.version)
print("Bundled Unicode:", ud.unidata_version)
assert ud.name("\U0001F600") == "GRINNING FACE"
assert ud.normalize("NFC", "e\u0301") == "\u00E9"

def classify(ch):
    return (f"U+{ord(ch):04X}",
            ud.category(ch),
            ud.name(ch, "UNASSIGNED/UNNAMED"))
print(classify("\u0378")) # Cn in current UCD (not a
                           # promise about all future releases)

A more comprehensive test suite should parse version-specific files such as NormalizationTest.txt, GraphemeBreakTest.txt and UCD property ranges. The test fixture must be paired with the implementation's advertised release: do not blame a Unicode 15 library for failing conformance vectors that require Unicode 18 assignments.

// JavaScript: assert data-level invariants
const cp = "\u{1F600}";
console.assert(cp.codePointAt(0) === 0x1F600);
console.assert([...cp].length === 1); // 1 scalar value
console.assert(cp.length === 2);      // 2 UTF-16 units
console.assert(new TextEncoder().encode(cp).length === 4);
console.assert("e\u0301".normalize("NFC") === "\u00E9");

// Runtime-specific behavior (emoji presentation/segmentation)
// belongs in separate, version-tagged compatibility tests.

Persist a manifest containing Unicode release, test dataset checksums, ICU/runtime version, normalization form and expected property/boundary counts. This permits reproducible regression comparisons and makes upgrades auditable rather than guesswork.

Common migration and security failures

  • “Unicode is backward-incompatible because new emoji look different.” Visual changes are not code point renumbering; diagnose font and layout components separately.
  • “Reject all unknown code points as invalid UTF-8.” Encoding validity and character assignment are distinct.
  • “Unicode version alone specifies what is on screen.” Font repertoire, platform shaping, locale and CSS all influence rendering.
  • “Normalization results never depend on version.” Strong stability has assigned-character conditions; new characters unknown to old versions deserve explicit tests.
  • “Property tables are interchangeable across releases.” Mixing versions risks inconsistent lookup and boundary algorithms.
  • “Updating ICU cannot affect existing data.” Derived search keys, segmentation outputs and identifier filters may change even when stored bytes do not.

Official release and stability references

  1. Unicode Consortium — Recent Releases, release dates and current specifications.
  2. Unicode Character Encoding Stability Policies, formal guarantees and their applicability.
  3. Unicode Standard 18.0.0, core specification and release details.
  4. UAX #44 — Unicode Character Database, properties, filenames and defaults.
  5. UAX #15 — Unicode Normalization Forms, canonical stability and conformance.
  6. UAX #29 — Unicode Text Segmentation, versioned boundaries.

Independent technical review of published Unicode stability policies; verify the precise data-version support of any runtime used in production.