<!-- https://minecraftdocs.dev/systems/commands/scoreboard-and-data — this page as markdown. The book's index: https://minecraftdocs.dev/llms.txt; the whole book in one file: https://minecraftdocs.dev/llms-full.txt -->

# Scores, teams and stored data

> Verified against **Minecraft 26.3** · Part XIII · `execute as @a store result score @s ticks_frozen run data get entity @s TicksFrozen`: one command writing a scoreboard through a callback the inner command has never heard of, and two of the three data models `execute store` exists to join.

Look at the sidebar on a well-built server and some of the names in it are
not players. *#total*, *constant*, *.timer* — rows belonging to nothing
alive. That is not a hack layered on top of the scoreboard; it is the
scoreboard working exactly as written, and one method override explains it.

`Entity.getScoreboardName` returns the entity's UUID string.
`Player.getScoreboardName` overrides it with the profile name. There is one
flat map from *string* to a `PlayerScores` — a row of scores, holding players by name, mobs by
UUID, and anything else you care to type. From that single override follows
the whole of scoreboard folk practice: **why fake players exist** (nothing
checks that a key belongs to an entity), **why a mob's score can never
appear in the tab list** (whose holder is built from a profile name), and
**why renaming a player orphans their scores**.

`execute store` is why four systems share this page. It is the only
construct in the game that takes the result of an arbitrary command and
writes it somewhere, and it has exactly **three** sinks — a score; a path
into a block, an entity or a storage; and a boss bar's value or maximum.
Each of those is a named thing on this server that a number can be written
into, and each brings its own model with it: the scoreboard, which is the
game's general-purpose *number per thing*; the NBT path language, a query
language for reaching into any tag at all, over command storage, which is a
place to put a tag belonging to no block and no entity; and the boss bar,
which turns out to be the scoreboard's shape one floor up. Teams are the
fourth, and they are here because they live in the scoreboard's package and
its class — read by systems that have nothing to do with scores.

The instinct they share is worth stating before the classes, because it
explains three otherwise-odd decisions: **the server is the only participant
that knows anything.** The client is sent scores it can draw and nothing
else — not an objective's criteria, not a score's lock bit, not which
objectives exist. There is **no serverbound packet in this whole system**. Every write a player asks for is a command.

## The cast

| class | what it decides | side |
|---|---|---|
| `Scoreboard` | six maps and nothing else, plus ten empty hooks for a subclass to fill. A pure data structure, used unmodified by the client | both |
| `Objective` | criteria, display name, render type, number format, auto-update — and a **back-pointer to the scoreboard**, so every setter reports its own change | both |
| `Score` | four mutable fields: the value, a lock bit, a display component and a number format. `ReadOnlyScoreInfo`, `PlayerScoreEntry` and `ScoreAccess` are the other three faces of it | both |
| `ScoreHolder` | the interface `Entity` implements, with `ScoreHolder.forNameOnly` minting anonymous ones — the most consequential class on the page | both |
| `PlayerTeam` | the **only** subclass of `Team`: the mutable state, the setters, a precomputed display style, and friendly-fire plus see-invisibles packed into one wire byte | both |
| `ServerScoreboard` | three fields — the server, `ServerScoreboard.trackedObjectives`, and one dirty boolean — and thirteen overrides, the ten hooks and three methods that change team membership and display slots, which between them broadcast what changed and mark the save dirty. It lives in `net/minecraft/server`, not beside `Scoreboard` | server |
| `NbtPathArgument` | 874 lines, the largest argument type in the game, and a whole query language: six node kinds and a depth limit of 512 | both |
| `DataCommands` | `/data`, over three `DataAccessor`s — `BlockDataAccessor`, `EntityDataAccessor` and `StorageDataAccessor`. Beside it, `ScoreboardCommand`, `TeamCommand` and `TriggerCommand` write the scoreboard, as `execute store` does | server |

`net/minecraft/world/scores` is the whole model, and every class in it ships in both jars. Beside it:
`CommandStorage`, a lazy façade over one `SavedData` *per namespace*, so
`minecraft:foo` and `mypack:foo` are different files; and `net/minecraft/network/chat/numbers`, holding `NumberFormat` with the three kinds `NumberFormatTypes` registers.

## One command, two models, and a number that lands in a third place

The figure follows that command from the engine's stage walk to the client's
sidebar; `BuildContexts` is the engine's walker over a parsed chain ([the
execution engine](the-execution-engine#the-queue-four-moments-apart)).

```mermaid
sequenceDiagram
    box Server
    participant BC as BuildContexts
    participant ExecC as ExecuteCommand
    participant DataC as DataCommands
    participant SS as ServerScoreboard
    participant SA as ScoreAccess
    end
    box Client
    participant CPL as ClientPacketListener
    end

    BC->>BC: as @a — one source becomes one per player
    BC->>ExecC: store result score — the modifier, once per source
    ExecC->>ExecC: storeValue — @s and the objective already resolved
    BC->>DataC: getData, the leaf, queued once per source
    DataC->>DataC: build the entity's whole save tag
    DataC->>DataC: follow the path, collapse the tag to one int
    DataC-->>ExecC: the result, to the source's callback
    ExecC->>SS: getOrCreatePlayerScore
    ExecC->>SA: set
    SA->>SS: onScoreChanged — only if changed or new
    SS->>CPL: ClientboundSetScorePacket — only for a displayed objective
    CPL->>CPL: handleSetScore — the owner is only a name
```

*One `execute store result score` for one player — the store is fixed on the source before the inner command runs, the number reaches it by callback, and two separate gates decide whether the write is a packet.*

The paragraphs below take its arrows.

**The store target is resolved before the inner command runs, not after.**
`ExecuteCommand.wrapStores` builds the store node as a **redirect with a
modifier**, not as a step after the leaf. The modifier resolves the score
holder and the objective against *this* source, then decorates the source
with a callback chained onto whatever was already there. So the store is a
property of the *source*, which is why several stores compose, and why each
forked player writes their own row without the leaf command knowing a
scoreboard exists.

**The result travels by callback, not by return value** — the source's own
callback, which is precisely why `execute store` works on a command typed in
chat whose *frame* callback is empty
([the execution engine](the-execution-engine)).

**A failing command under `store result` writes 0**, whichever kind it is.
On the custom-executor path that is `CustomCommandExecutor.WithErrorHandling` doing what it does for every executor built on it ([the execution
engine](the-execution-engine#a-result-is-a-flag-and-a-number-and-only-a-function-tag-sums-them)),
so a failing `/function` writes 0. For an ordinary leaf the result consumer is
driven by Brigadier, and
`ContextChain.runExecutable` catches the `CommandSyntaxException` and calls
the consumer with *success false, result 0* before rethrowing. The game
hands that consumer straight through from the source's own callback, so the
two paths agree: a store target written by a command that threw holds zero,
not its previous value.

**`/data get` builds the whole entity to read one field.**
`EntityDataAccessor.getData` produces the entity's entire save tag, freshly,
and the path then walks it. That is the real cost of `/data get`, and it is
why a per-tick `/data get` on a busy entity is a measurable expense.

**Four rules collapse a tag to one integer.** A numeric tag floors its double
value; a collection and a compound both yield their *size*; a string yields
its length. Nothing yields the value you might have meant:
`data get entity @s Inventory` returns the number of stacks you are
carrying, not 36 — `Inventory.save` writes only the non-empty slots, so the
collection whose size you get is as short as your inventory is empty. A path matching more than one tag is an error; a path matching
none is a different error.

**The write can be a silent no-op, and may never reach the wire at all.**
`ScoreAccess.set` writes nothing and sends nothing when the value is
unchanged and the score is not new — unless the objective has auto-update
on, in which case it refreshes the display name first and a changed display
counts as a change on its own. And `ServerScoreboard.onScoreChanged` is
gated on the objective being in `ServerScoreboard.trackedObjectives`, whose
only entrance is occupying a display slot.

## Why a write is a handle

`Scoreboard.getOrCreatePlayerScore` does not return a `Score`. It returns a
`ScoreAccess` — an anonymous object closing over the score, the objective
and the holder, plus **two decisions computed once, at handle-creation
time**: may this be modified (did the caller ask for a force-writable
handle, or is the criteria not read-only), and was this score *newly
created* by the lookup that produced this handle.

That is the whole answer to "why not a setter". A setter would have to
re-derive the first fact on every call, and the second is unrecoverable
after the fact — once the `Score` exists, nothing can tell whether *this*
call created it. Newness is what decides whether an unchanged value still
needs a packet, so it has to survive from the lookup to the write. The
handle is also the one place that knows when to fire the change hook, which is why a write goes through `ScoreAccess.set`, `ScoreAccess.add`, `ScoreAccess.increment`, `ScoreAccess.reset`, `ScoreAccess.lock`, `ScoreAccess.unlock`, `ScoreAccess.display` or `ScoreAccess.numberFormatOverride`; only removal — `Scoreboard.resetSinglePlayerScore`, `Scoreboard.resetAllPlayerScores` and `Scoreboard.removeObjective` — and loading from the save go round it.

Nothing here is ticked. `MinecraftServer` mentions the scoreboard
five times in total — the field, the constructor, the load, the getter, and
one call to `ServerScoreboard.storeToSaveDataIfDirty` inside
`MinecraftServer.saveAllChunks`. There is no periodic sweep and no
dirty-queue drain: every mutation that reaches the wire sends its packet synchronously, inside the call that made it, and there is **one scoreboard per server, not
per level**, so scores and teams are global across dimensions.

## Names that belong to nobody, and the `#` that hides them

The opening's fake players are not one mechanism but two, and the `#` in
front of *#total* does a different job in each.

In `ScoreHolderArgument` a leading `#` skips entity resolution entirely, so
the token is taken as a literal name and no lookup happens. Its literal side has four branches in order — the wildcard `*`, a `#` name, a UUID searched across every level, and an online player — and the UUID and player branches fall back to a bare name when they find nothing. The wildcard does not: with
no tracked holders at all it throws.

In the sidebar the same character means something unrelated:
`PlayerScoreEntry.isHidden` filters the row out of the display. One
character, two mechanisms, and together they are the whole
hidden-fake-player idiom — a row that can be written by name, read by name,
and never drawn.

Nothing about that requires an entity to exist. `ScoreHolder.forNameOnly`
mints a holder from a string — it is all `ClientPacketListener.handleSetScore`
ever builds from a score packet — and the scoreboard's one flat map does not
care where the string came from.

## What a criterion can be, which is nearly anything

`ObjectiveCriteria` looks like an enum of eleven values and is not.
Forty-three constants exist, thirty-two of them generated — sixteen
team-kill and sixteen killed-by-team criteria, one per team colour. Six are
read-only: health, food, air, armour, experience and level.

And then the tail. **`Stat` extends `ObjectiveCriteria`**, so every
statistic in the game *is* a criterion, and `ObjectiveCriteria.byName`
parses a colon-separated name by looking the left half up as a stat type and
the right half in that stat type's own registry.
`minecraft.mined:minecraft.stone` is not a special case; it is the
statistics registry addressed through a string. Nine stat types over the
block, item, entity-type and custom-stat registries make thousands of valid
criteria names, which is why `/scoreboard objectives add` accepts far
more than the forty-three bare criteria names — its suggestions offer those
*and* every stat name in every stat type's registry.

The identity-keyed reverse index is what makes that cheap.
`Scoreboard.objectivesByCriteria` is an **identity** map from criteria to
the objectives watching it, `ServerPlayer.awardStat` hands the `Stat` object
itself to `Scoreboard.forAllObjectives`, and object identity finds the
watchers — sound only because each stat object is interned, one per value, in its `StatType`.

Criteria-driven scores are the one part of this page with a schedule, and it
is narrower than it sounds. `Scoreboard.forAllObjectives` has **seven call
sites and every one is in `ServerPlayer`**: one inside the helper each of the six read-only criteria is updated through, one for the death count, two for the kill counts, one that fires
both team-kill criteria, and two for statistics — one awarding, one
resetting. No *criterion* is driven from `Entity`, `LivingEntity` or
`Mob`, so a skeleton killing a zombie increments nobody's kill count —
though `Entity.load` does write to it, calling `Scoreboard.addPlayerToTeam` when the tag it loads names a *Team* — a field the game reads but never saves, so it arrives only in a tag a command or an item supplies. The six read-only criteria are change-detection diffs — six
consecutive comparisons against remembered fields — living in
`ServerPlayer.doTick`, which runs in the **connection** phase, after the
levels have ticked ([the level tick](../server/server-level-tick)). So
damage taken during the level tick reaches the scoreboard, and the wire,
later in the same tick rather than during it.

## The path language, and the accessor that is coarser than it looks

Six node kinds — a named child, a match on an object, a match on the root
object, a match on a list element, all elements, and an index — with a depth
limit of 512.

The elegant part is **creation**. The parent-creating walk goes through the
nodes and, for each one, asks *the next node* what shape its parent has to
be: a named child wants a compound, an index wants a list. So a *set* through *a.b.c* materialises two compounds without any node knowing more than its own type — though an index never creates an element, so through `a.b[0].c` the walk makes *a* and an empty list *b*, finds nothing at `[0]`, and fails. Removal is the same walk with a
plain lookup instead, so it never creates.

The three accessors, by contrast, are coarse. `DataAccessor` has two methods
that matter — read the whole tag, write the whole tag — and **no path-aware
write anywhere**. Every `/data modify` is *read everything, mutate in
memory, write everything back*, which is why a block-entity write reloads
the block entity and marks the chunk dirty, and why an entity write
round-trips the entity through its own load path and then restores the UUID
by hand, because loading would have overwritten it. What each accessor
*does* contribute is its own grammar subtree, which is how `DataCommands`
builds the target half and the source half of every subcommand from one list
of three providers applied twice.

## Teams, which systems far from scores read

`Team` declares everything a reader asks for and `PlayerTeam` is its only
subclass. What makes teams worth their own paragraph is who else consults them: collision through
`EntitySelector.pushableBy`; nametag visibility through
`LivingEntityRenderer.shouldShowName`; invisibility through
`Entity.isInvisibleTo`; friendly fire through `Entity.doTeamsAllowDamage`; and
death-message routing through `ServerPlayer.die`. Only `ServerPlayer.die` and
`LivingEntityRenderer.shouldShowName` are reached from anywhere near one
place; `EntitySelector.pushableBy` has six call sites, `Entity.doTeamsAllowDamage` seven, among them `Player.canHarmPlayer` and the attacks of a few mobs, and `Entity.isInvisibleTo` two. A sixth system is joined to teams both ways. Every team join, leave and modification calls through to `ServerWaypointManager` to remake the locator bar's connections, and remaking one reads the team's colour to re-colour its icon.

Two team behaviours are worth pinning. `Team.isAlliedTo` is **reference
equality**, so two teams with byte-identical settings are never allied;
every "same team?" test in the game is really "same object?", safe only
because `Scoreboard.teamsByName` is the single owner of every instance. And
a team has two visibility settings of which only one ships: the wire
parameters carry nametag visibility, while death-message visibility has a single reader in play, `ServerPlayer.die`, and the client never learns the rule
because it does not need to. Nametag visibility also *widens*: when an
entity has a team, `LivingEntityRenderer.shouldShowName` returns from the
team switch directly and never reaches the checks that hide a name behind
F1, for the camera entity, or for a vehicle — so a mob on a team set to
*always* keeps its name through the HUD toggle.

## What the client is ever told

### Five packets, and one field that gates four of them

Five packets, all server → client, and no serverbound counterpart exists:
`ClientboundSetObjectivePacket`, `ClientboundSetDisplayObjectivePacket`,
`ClientboundSetScorePacket`, `ClientboundResetScorePacket` and
`ClientboundSetPlayerTeamPacket`. All five reach every player, through `PlayerList.broadcastAll` or a loop over the player list — no distance filter, no dimension filter
([what the client is told](../networking/what-the-client-is-told)).

An objective in no display slot **does not exist on the network**.
`ServerScoreboard.onObjectiveAdded` sends nothing at all; the only path into
the tracked set is `ServerScoreboard.setDisplayObjective`. A scoreboard with
two hundred objectives and an empty sidebar costs zero bandwidth — and
putting one *into* a slot then ships every score it holds, to every player,
at once. The join burst is the same shape:
`PlayerList.updateEntireScoreboard` sends every team with its full member
list, then walks all nineteen display slots and ships each distinct
occupying objective with all of its scores.

### What it is not told, and how it draws anyway

What the client is told is also less than it looks. `ClientPacketListener`
constructs every objective it receives with `ObjectiveCriteria.DUMMY`, so a
client cannot tell a health objective from a dummy one — it only knows to
draw hearts — and the score packet carries no lock bit, which is why
`/trigger`'s suggestions have to be computed on the server.

How a score is *drawn* is settled by a third object again, and it has its
own fall-back order. A `NumberFormat` may ignore the number entirely —
`FixedFormat` renders a constant component whatever the score is — and
resolution runs per-score override, then per-objective, then a per-site
default: red in the sidebar, yellow in the tab list, unstyled below the
name. Below-name numbers are the odd one out: they are computed in `Entity`
rather than in a renderer, and their range is an **attribute**,
`Attributes.BELOW_NAME_DISTANCE`, syncable, default ten, maximum 512, so a
server can change how far away a player's below-name score is legible, per
entity ([attributes](../entities/attributes)).

There is a third route by which a score reaches a client, and it carries no
score packet at all: a `{"score":…}` or `{"nbt":…}` in a text component.
`ScoreContents` and `NbtContents` resolve on the **server**, against the
authoritative scoreboard, and put the *result* on the wire — never the
reference ([text components](../foundations/text-components)). A
`/tellraw` is a photograph, not a subscription.

## What survives a restart, and what does not

Saving is one boolean for the entire scoreboard, cleared by re-packing the
whole thing, and it happens only when the world is saved — the autosave,
`/save-all`, or shutdown. A crash thrown by the tick itself is a shutdown: it falls into the same *finally*, which calls `MinecraftServer.saveAllChunks`, whose very first statement is `ServerScoreboard.storeToSaveDataIfDirty`. **What loses a score is an ending that never reaches that method** — a crash relayed from a worker thread, which the shutdown's own drain throws again before the save, a watchdog kill, a *kill -9*, a power cut — and then every score since the last autosave goes with it ([how a
server dies](../server/how-a-server-dies#what-you-lose-if-you-kill-the-process)). `ScoreboardSaveData` sits under
`minecraft:scoreboard` beside the world, with one command-storage file per
namespace, both through the data fixer
([level data and rules](../../reference/level-data-and-rules)). The NBT
field names are the archaeology — *Objectives*, *PlayerScores*,
*DisplaySlots*, *Teams*, and inside them *Name*, *CriteriaName*,
*RenderType*, *Locked* — capitalised, and preserved by codec.

## The third sink is a boss bar, and it is this page's shape again

`execute store result bossbar <id> value` writes into a `CustomBossEvent`,
and a reader who has the scoreboard already knows the model. A boss bar is a
named server-side thing holding a number, saved with the world, broadcast to
the players attached to it, and writable only by a command.

`BossEvent` is the shape everything shares: an id, a name, a *progress* float
from zero to one, a `BossEvent.BossBarColor`, a `BossEvent.BossBarOverlay`,
and three booleans that ask the client to darken the screen, play the boss
music and draw the world fog. `ServerBossEvent` adds the live membership — a
`Set<ServerPlayer>` — and turns every setter into a broadcast, which is the
whole of the sending side the book had not named: one
`ClientboundBossEventPacket` with six operations, add and remove and one
update apiece for progress, name, style and properties. There is no
serverbound counterpart, exactly as there is none for a score. Four things in
the game own one: `WitherBoss`, `Raid`, `EnderDragonFight` — and
`CustomBossEvent`, which is `/bossbar`.

What `CustomBossEvent` adds is the part that rhymes. It keeps an integer
`CustomBossEvent.value` and an integer `CustomBossEvent.max`, derives the
float progress from them on every write, and holds its membership as a
**`Set<UUID>`** *beside* the superclass's set of live players. The UUID set is
what persists and what `/bossbar set … players` edits; the live set is
maintained by `CustomBossEvent.onPlayerConnect`, which re-attaches a player
whose UUID is on the list, and by `CustomBossEvent.onPlayerDisconnect`, which
calls the superclass's removal, so that the UUID stays. A bar's
membership outlives the session the same way a score outlives the entity.

`CustomBossEvents` is the `SavedData` that holds them, one file at
*data/minecraft/custom_boss_events.dat* ([level data and
rules](../../reference/level-data-and-rules)), and its NBT is the same
archaeology as the scoreboard's — *Name*, *Visible*, *Value*, *Max*,
*Players*, capitalised, preserved by codec. `BossBarCommands` is the write surface, at gamemaster, and an overload of `ExecuteCommand.storeValue`, the score sink's own name, is the store sink: a sibling of the score sink
rather than the same code, chaining the same kind of callback onto the source
and writing the value or the maximum instead of a `ScoreAccess`. What the
client does with the packet — the interpolation, the sky it darkens — is [the
HUD](../client/hud#the-hidden-flag-travels-two-ways).

## Questions players ask

**Why is my sidebar not `DisplaySlot.SIDEBAR`?** If the local player is on a
team *with a colour*, `Hud` uses that colour's own display slot and falls
back to the plain sidebar otherwise. The colour-to-slot mapping lives on
`TeamColor`, not on `DisplaySlot`, and it is what the sixteen team sidebars
are for. The sidebar shows fifteen rows and **hides before it cuts**: hidden
rows are filtered out, then the rest sorted by value descending and name
case-insensitively, and *then* truncated to fifteen.

**Why did `/trigger` say the objective is not enabled?** `Score`'s lock bit
starts **locked** and its codec defaults **unlocked**, so a score created by
`/scoreboard players set` is locked while a score loaded from a file that
omits the field is not. `/trigger` is the only command an unprivileged
player can use to write a score, and its gate is three-part — the criteria
must be the trigger criteria, the score must already exist, and it must be
unlocked — and the command re-locks it immediately, so each *enable* buys
exactly one use. It is also, with `/teammsg` (and its `/tm` alias), one of the two commands in this area registered with no permission requirement at all.

**Why did my `execute store` throw an internal error?** Because
`/scoreboard` refuses a read-only objective and `execute store` does not
check. The command resolves its write targets through
`ObjectiveArgument.getWritableObjective`; `ExecuteCommand.wrapStores` uses
the plain lookup and never asks for a force-writable handle. So
`execute store result score @s <a health objective>` reaches
`ScoreAccess.set` with modification disallowed and raises a raw runtime
exception from inside a result callback rather than a command error. The
same hole is reachable through `/scoreboard players operation` with `><`,
the one operator that writes both sides.

**Why did my `execute store` into a data target do nothing at all?** Its
read-mutate-write is wrapped in a catch with an **empty body**: a player target, an uncreatable path, a path too deep — no message, no failure, no write. The score sink has no such
catch.

**Why can I read a player's NBT but not write it?** The entity accessor's
write path rejects any `Player` before doing anything else, and the read
path has no such check. That one asymmetry is why every player-NBT technique
is read-only. Relatedly, a no-op is a **hard failure** across the area — `/data merge`, `/data modify`, `/data remove` and `/scoreboard players enable` all throw when they changed nothing, as do most of `/team`'s setters and every one of `/bossbar`'s, which
makes them usable as conditionals in a function, the same choice
`/advancement grant` makes.

**Do a mob's scores survive it despawning?** They survive *unloading* and
die with the mob: `Scoreboard.entityRemoved` runs from the level's
destruction callback and is gated on the entity being both non-player and
not alive.

## Where to look

`Scoreboard` for the six maps, `ScoreAccess` for why a write is a handle,
and `ServerScoreboard.trackedObjectives` for the one field that decides which objectives and scores a client's scoreboard ever holds. `ObjectiveCriteria.byName` for the statistics bridge,
`NbtPathArgument.NbtPath` for the tidiest piece of the area, and
`ExecuteCommand.wrapStores` for the one that makes `execute store` stop
feeling like magic.

---

*Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes `tools/verify_names.py`.*
