<!-- https://minecraftdocs.dev/systems/player/hunger-and-experience — 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 -->

# Hunger and experience

> Verified against **Minecraft 26.3** · Part VIII · Two bars above the hotbar that the server owns outright: one you empty by sprinting, one you fill by mining, and neither of them is quite the number the interface draws.

The food bar and the experience bar look like the same kind of thing: a
number the server keeps and sends you. They are, and they are also the two
counters a player argues with most — the meal that does not seem to fill
you, the levels that vanish into an anvil. Both are worth following for the
same reason: neither is quite what the interface suggests. **There is no
method called *eat*** on `Player` or `LivingEntity` — eating is a walk over
the components of a stack that happens to end in `FoodData.eat` — and the
experience packet is change-detected on the **total** alone, so every
mutation that changes only your level has to poison the last-sent value or
the bar will not move.

## The cast

| class | what it decides | thread |
|---|---|---|
| `FoodData` | the food bar, saturation and exhaustion; both sides hold one, but only the server ticks it | both (`FoodData.tick` takes a `ServerPlayer`) |
| `FoodProperties` | how much a given item is worth, as a data component | both |
| `Consumable` | how long eating takes, what it sounds like, and what else it applies | both |
| `Player` | the four experience fields, the enchanting seed among them, and the level curve | both |
| `ExperienceOrb` | a value and a multiplicity, wandering toward you | both |
| `ServerPlayer` | the change detection that turns any of this into a packet | Server |

Both halves hang off `ServerPlayer.doTick`, the connection-driven half of
[the two-phase
tick](the-two-phase-tick#phase-two-what-this-player-would-do-if-it-simulated-itself);
the level's entity tick touches
essentially none of it. The order inside that half matters: item use is
resolved, then `Player.aiStep` runs `ServerPlayer.tickRegeneration` — which
*is* the Peaceful refill, its whole body gated on that difficulty and the
natural-regeneration rule — and the
orb pickup, then **`FoodData.tick`**, and then the
change-detection block that emits the packets. So a meal eaten this tick and
a Hunger effect's exhaustion from this tick are both visible to
`FoodData.tick` in the same tick, and the resulting health and food reach
the client in that tick too.

## The food bar is four numbers and a file of constants

**`FoodData`** (`world/food`) is a value bag with no back-reference to the
player: `FoodData.foodLevel` (20), `FoodData.saturationLevel` (5.0),
`FoodData.exhaustionLevel` and `FoodData.tickTimer`. Its surface is the two
`FoodData.eat` overloads (one taking a `FoodProperties`, one taking a
nutrition and a saturation modifier), plus `FoodData.addExhaustion` (which caps at
40), `FoodData.needsFood`, `FoodData.hasEnoughFood`, `FoodData.setFoodLevel`,
`FoodData.setSaturation`, the two accessors, and `FoodData.tick` — whose
signature is `FoodData.tick(ServerPlayer)`, **server-only by type**. It saves
as four loose keys in the player tag, not a sub-compound.

**`FoodConstants`** names most of the thresholds in the system —
`FoodConstants.MAX_FOOD`, `FoodConstants.HEAL_LEVEL`,
`FoodConstants.HEALTH_TICK_COUNT`,
`FoodConstants.HEALTH_TICK_COUNT_SATURATED`,
`FoodConstants.EXHAUSTION_DROP`, `FoodConstants.EXHAUSTION_HEAL`,
`FoodConstants.EXHAUSTION_SPRINT`, `FoodConstants.EXHAUSTION_MINE`,
`FoodConstants.EXHAUSTION_ATTACK`, `FoodConstants.SPRINT_LEVEL`,
`FoodConstants.SATURATION_FLOOR` and the saturation-quality ladder from
`FoodConstants.FOOD_SATURATION_POOR` to
`FoodConstants.FOOD_SATURATION_SUPERNATURAL` — and **each is a compile-time
constant**, which javac writes in wherever it is used, so a decompile of
`FoodData` shows the numbers themselves and this page gives them as numbers.
The file's one method, `FoodConstants.saturationByModifier`, turns a food's
saturation modifier into saturation. A few numbers have no name there at all,
the exhaustion cap of 40, the starvation floors and the Peaceful refill's
periods among them.

Before the tick can spend exhaustion, something has to produce it, and the
economy is smaller than players assume. **Walking costs nothing**:
`ServerPlayer.checkMovementStatistics` charges the walking and the crouching
branch zero per block, the zero `FoodConstants.EXHAUSTION_WALK` and
`FoodConstants.EXHAUSTION_CROUCH` name. What does cost you is sprinting, jumping,
swimming (and walking in or under water, at the same rate), mining, attacking, the
Hunger effect, the `ApplyExhaustion` enchantment effect, and being hurt — the
last of which is data-driven, since `DamageSource.getFoodExhaustion` reads
the damage type. `Player.causeFoodExhaustion` charges 0.1 for a melee hit that lands,
which is the figure [the sword swing](the-sword-swing) and [the
spear](the-spear) both end on. And in creative or spectator the whole
economy is disabled in one line: `Player.causeFoodExhaustion` returns
immediately for invulnerable abilities.

What `FoodData.tick` then does with that budget is one exhaustion rule and a
three-way chain, tested **in the order below**, first match only — and the
figure is that order, with the conditions themselves in the table under it.

```mermaid
flowchart TD
    EX{"exhaustion<br/>above 4.0?"}
    EX -- "yes" --> DRAIN["spend 4.0 — saturation first, then the bar"]
    EX -- "no" --> C1
    DRAIN --> C1{"1 · heal fast?"}
    C1 -- "yes" --> FAST["every 10 ticks, charged back as exhaustion"]
    C1 -- "no" --> C2{"2 · heal slowly?"}
    C2 -- "yes" --> SLOW["every 80 ticks, for 6.0 of exhaustion"]
    C2 -- "no" --> C3{"3 · starve?"}
    C3 -- "yes" --> STARVE["every 80 ticks, above the difficulty's floor"]
    C3 -- "no" --> RESET["none of the three — the timer resets"]
```

*A chain, not a fan: each numbered diamond after the first is only reached
because the one above it said no, which is the whole of why the three are
exclusive.*

| tested in this order | the condition | and then |
|---|---|---|
| 1 · heal fast | the game rule on, saturation left, hurt, and the bar at 20 | every 10 ticks: heals a sixth of the saturation it spends, and charges that much back as exhaustion |
| 2 · heal slowly | the game rule on, hurt, and 18 food or more | every 80 ticks: half a heart, for 6.0 of exhaustion |
| 3 · starve | food at zero — and **not** gated on the game rule | every 80 ticks: half a heart of `DamageTypes.STARVE`, above the difficulty's floor |
| 4 · none of them | — | `FoodData.tickTimer` goes back to zero |

The order is what makes the three exclusive, and it is why a full bar with
saturation left heals you up to eight times faster than a bar at eighteen: both
conditions hold at twenty food, and the fast one is tested first.

The game rule is `GameRules.NATURAL_HEALTH_REGENERATION`, which lives in
`world/level/gamerules` with typed `GameRule` lookups ([level data and
rules](../../reference/level-data-and-rules#game-rules-are-a-registry)). The starvation hit only
lands if health is above five hearts, or above half a heart on Normal, or
unconditionally on Hard: five hearts is the floor on Easy and Peaceful, half
a heart on Normal, and death on Hard. `DamageTypes.STARVE` is declared with
zero exhaustion, so starving does not feed itself.

## Eating is a component walk

**`FoodProperties`** (`DataComponents.FOOD`) is three things: nutrition,
saturation and *can always eat*. That is the whole of what a meal is *worth*.
Everything about how long it takes, what it looks like and what else it
applies is a different component on the same stack — `Consumable`, and [using
an item](../items/using-an-item#the-two-paths-side-by-side) owns it.

The two meet through an interface, and that is the answer to the missing
*eat* method. `FoodProperties` reaches the player by implementing
**`ConsumableListener`**,
and `Consumable.onConsume` walks every component of that type on the stack.
It is not the only implementation — `PotionContents`,
`SuspiciousStewEffects` and `OminousBottleAmplifier` implement it too, and
`PotionContents` is how drinking applies an effect, which is where this page
and [status effects](status-effects#what-an-effect-is) meet in one method. Two routes reach
`FoodData.eat` without any of that: `CakeBlock.eat`, and the saturation
effect, both using the overload that takes a nutrition and a saturation
modifier.

The walk itself is five hand-offs on the server and one line out to the
client, and the packet leaves before any of the five have run.

```mermaid
sequenceDiagram
    box transparent the server
    participant SP as ServerPlayer
    participant IStack as ItemStack
    participant Cons as Consumable
    participant FP as FoodProperties
    participant FD as FoodData
    end
    box transparent the client
    participant CPL as ClientPacketListener
    end

    SP->>SP: updateUsingItem — the zero check is server side only
    SP->>CPL: ClientboundEntityEventPacket(9), before the walk starts
    SP->>IStack: finishUsingItem — the item decides what finishing means
    IStack->>Cons: onConsume — walks every ConsumableListener on the stack
    Cons->>FP: onConsume — the food component is one such listener
    FP->>FD: eat — nutrition and pre-multiplied saturation, clamped
    Cons->>Cons: onConsumeEffects — server only, then consume(1)
    CPL->>CPL: the same walk again, locally — the food half unguarded
    SP->>FD: tick — exhaustion drain, then regen or starvation
    SP->>CPL: ClientboundSetHealthPacket — the prediction is overwritten
```

*The second arrow leaves before the server has eaten anything, and it and the
health packet at the end are the figure's only arrows to the client (the meal's
sounds go as broadcasts): the five arrows after it on the left are the walk,
and the client's one self-call is that walk run again, its food half with no
side guard.*

`Consumable.canConsume` consults `Player.canEat` only when the stack has
`DataComponents.FOOD` and the user is a player — potions and milk are
ungated — and `Player.canEat` itself passes for *invulnerable abilities*,
*can always eat*, or `FoodData.needsFood`. An item whose
`Consumable.consumeTicks` is zero is consumed instantly with no animation.
After the food lands, `ItemStack.finishUsingItem` applies
`DataComponents.USE_REMAINDER` and `DataComponents.USE_COOLDOWN` ([using an
item](../items/using-an-item#the-two-endings)).

The *decision* to finish is server-only, but the client replays the meal on
an entity event and runs `FoodProperties.onConsume` and its `FoodData.eat`
locally, with no side guard on it — which is why the hunger bar's jump is
predicted while a chorus fruit's teleport is not ([using an
item](../items/using-an-item#the-meal-tick-by-tick)). The prediction barely
outlives the packet that caused it when the meal moved the bar: the server
then sends `ClientboundSetHealthPacket` from the same tick, and `ClientPacketListener.handleSetHealth` overwrites food
and saturation outright, and routes health through
`LocalPlayer.hurtTo`, which works out the delta first so the damage flash
still plays. The client also *reads* its food data for three decisions of its
own: whether you may start eating at all (`Player.canEat`), sprinting, which is
gated on having more than six food *or* being able to fly, and the HUD's
food-bar jitter, which reads saturation.

What crosses the wire for all of this is four packets of its own, beside the
sounds it broadcasts —
`ClientboundEntityEventPacket` for the finished meal, `ClientboundSetHealthPacket`
(health, food and saturation together, to that player only),
`ClientboundSetExperiencePacket` (progress, level and total) and
`ClientboundTakeItemEntityPacket` for the orb pickup animation — and what is
data-driven is `DataComponents.FOOD`, `DataComponents.CONSUMABLE`,
`DataComponents.USE_EFFECTS`, the consume effects a `Consumable` lists (typed
by `Registries.CONSUME_EFFECT_TYPE`), the damage types' exhaustion and the
three game rules this page names.

Eating slowdown is a third component again — `UseEffects`, on *every* item,
which is why a meal and a drawn bow cost you exactly the same speed ([using
an item](../items/using-an-item#moving-while-you-use)). It has nothing to
do with the food: it is read while any item is held out, and a spear is the
one definition that turns it off ([the spear](the-spear#what-an-item-needs-to-be-a-spear)).

## The other bar, and the number it is really watching

`Player.experienceLevel`, `Player.experienceProgress`,
`Player.totalExperience` and `Player.enchantmentSeed`, plus
`Player.takeXpDelay` and `Player.lastLevelUpTime` — which exists only to
throttle the level-up sound — are the whole of it. The arithmetic is
`Player.giveExperiencePoints`, `Player.giveExperienceLevels` and
`Player.getXpNeededForNextLevel` — the three-segment curve with corners at
levels 15 and 30.

Where orbs come from is worth naming, because two game rules gate it:
`LivingEntity.dropExperience` requires the experience not to have been
consumed already, and either an always-dropper or a recent player kill with
`GameRules.MOB_DROPS` on and the entity's own
`LivingEntity.shouldDropExperience` agreeing; a player's own death drop is
`Player.getBaseExperienceReward`, seven per level capped at 100, unless
`GameRules.KEEP_INVENTORY` — or unless you are a spectator, which drops
nothing.

The packet that draws the bar watches **one** of those four fields, and it is
not the one on the bar. `ServerPlayer.lastSentExp` is compared against
`Player.totalExperience`, and `ClientboundSetExperiencePacket` goes out only
when the two differ. Every mutation that changes the *level* without changing
the total — `ServerPlayer.setExperienceLevels`, `Player.giveExperienceLevels`,
enchanting, respawn — therefore has to poison the last-sent value by hand
before the bar will move. Nothing in the game changes a level and lets the
change detection notice: `ServerPlayer`'s version of each of those methods
remembers to lie to it, and a new one that forgot would produce a client whose
level is stale until the next orb.

**`ExperienceOrb`** is an `Entity` with `ExperienceOrb.DATA_VALUE` synched
and `ExperienceOrb.count`, `ExperienceOrb.age`, `ExperienceOrb.health` and
`ExperienceOrb.followingPlayer` unsynched — though `ExperienceOrb.age` and
`ExperienceOrb.followingPlayer` are still mutated by the client's own tick,
which runs the follow behaviour locally. `ExperienceOrb.health` is not: once
the orb is made or loaded, only `ExperienceOrb.hurtServer` writes it.
`ExperienceOrb.awardWithDirection` splits an amount into denominations via
`ExperienceOrb.getExperienceValue` (a fixed ladder from 2477 down to 1) and
calls `ExperienceOrb.tryMergeToExisting` for each; `ExperienceOrb.award` is
a one-line delegate to it. Merging is by **count, not value**: an orb
carries one value and a multiplicity. The merge candidate search picks a
*random* group number below `ExperienceOrb.ORB_GROUPS_PER_AREA` and only
merges into orbs whose id is congruent to it — which keeps same-valued orbs
in separate groups that never merge across, rather than reducing the scan.

## Questions players ask

**Why does a meal not seem to fill me?** Because the number you cannot see is
clamped to the one you can. `FoodData.eat` adds nutrition to the bar and
saturation behind it, and then **clamps saturation to the new food level** —
so the emptier you are when you eat, the more of the meal's saturation is
thrown away. A golden carrot carries 14.4 of it — nutrition 6 at a modifier
of 1.2, doubled by `FoodConstants.saturationByModifier` — and eaten on an
empty bar it leaves you at six food and six saturation, with more than half
of what it was worth discarded. The reserve that funds fast healing is
exactly what an emergency meal loses. At the other end the
meal does not happen at all: `Player.canEat` refuses at a full bar unless the
food is *can always eat* or your abilities are invulnerable.

**Can my saturation sit above my food bar?** Only on Peaceful with natural
regeneration on, and only by less than a point at the top:
`ServerPlayer.tickRegeneration` adds a whole point of saturation whenever it is
below 20, while `FoodData.eat` clamps saturation to the food level. Only one of
the two respects the clamp.

**Why is my client's saturation out of date?** Because it is sent but not
change-detected. `ClientboundSetHealthPacket` carries a full float, yet the
server only notices whether saturation became *zero* — so your client's
saturation does not update until health or food moves or it crosses zero, and
the one thing the HUD reads from it, whether it is zero, is the one thing sent
on time.

**Why is the Standard Galactic gibberish stable until I enchant?** Because
`Player.onEnchantmentPerformed` subtracts the level cost *and* re-rolls
`Player.enchantmentSeed`, while `AnvilMenu` — which also spends levels,
through `Player.giveExperienceLevels` — does not. A seed that loads back as
zero is re-rolled on read. [Enchanting](../items/enchanting#where-the-randomness-comes-from)
owns what the seed is for. The levels the anvil eats and the seed the table
reads are fields on the same object, and only one of the two spenders touches
both.

**Why does an orb repair my pickaxe before it reaches my bar?** Because
`ExperienceOrb.playerTouch` runs `ExperienceOrb.repairPlayerItems` first —
mending, through `EnchantmentEffectComponents.REPAIR_WITH_XP` — and only the
remainder becomes experience; that method then calls itself with the
leftover. One orb entity can also be picked up many times: it carries a
count, decremented per absorption behind a two-tick `Player.takeXpDelay`,
and a player touches **one orb per tick**, chosen at random from those it is
touching — so at most one is absorbed every two ticks. The pickup sweep buckets orbs separately from items for exactly
that purpose.

## Where to look

**`FoodData`** is short and is the food half entire — read it first, then
**`FoodConstants`** beside it for the names of most of the numbers a decompile
shows written into `FoodData`. **`Foods`** is where the
built-in `FoodProperties` values live, if you want to know what a carrot is
worth.

For the walk that reaches it, open **`Consumable.onConsume`** and follow it
into **`ConsumableListener`**: `FoodProperties` is one implementation of
four, and seeing the other three is what makes the missing *eat* method
obvious. The experience half is one method,
**`Player.giveExperiencePoints`**, plus **`ExperienceOrb`** for the
denomination ladder and the merge. Then the two packets the whole page is
about: **`ClientboundSetHealthPacket`**, which carries a number it does not
change-detect, and **`ClientboundSetExperiencePacket`**, which
change-detects a number it is not drawing.


---

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