Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Trees

Verified against Minecraft 26.2 · Part XII · One sapling grows: five pluggable parts over one algorithm, a ceiling the crown’s size was decided before, and the dark-oak sapling that will never grow on its own.

Plant a single dark-oak sapling, feed it bone meal until you run out, and nothing happens. Nothing is wrong with the sapling; there is simply no tree for it to become. TreeGrower holds up to six configured features per species — a normal tree and a mega tree, each with a secondary variant, plus two flowering variants — and TreeGrower.DARK_OAK fills in exactly one of them, the mega tree. The single-sapling slot is left empty, and TreeGrower.growTree finds nothing to place. The best-known growth rule in the game is implemented as an absence.

Which is a good introduction to this page, because the whole tree kit works like that: the thirty-nine configured tree features are one algorithm, TreeFeature, with five slots in it, and almost everything you can say about how a cherry differs from a mangrove is a statement about what is in the slots. Features and placement is how a tree gets a position and whether it is attempted at all; this is what happens after Feature.place is entered.

The cast

classits slotwhat varies
TreeFeaturethe algorithm — final, one implementation, no subclassesnothing
TreeConfigurationnine fields: five of them are the parts below, three are BlockStateProviders (trunk, foliage, and the dirt column laid under the trunk) and one is the ignore vines flageverything
TrunkPlacerwrites the logs, returns where crowns hang9 registered types
FoliagePlacerwrites the leaves around one attachment11 registered types
RootPlacerwrites roots, and may lift the trunk off the ground1 registered type
FeatureSizethe clearance profile — a horizontal radius per height2 registered types
TreeDecoratorruns afterwards over what was placed10 registered types
FoliagePlacer.FoliageAttachmentthe only channel from trunk to crown: a position, a signed radius nudge, and is the trunk under me two-by-two

Every one of those five is a codec-dispatched type in a built-in registry, so a data pack composes trees freely and cannot add a new kind of placer (the data-driven type pattern).

One algorithm, five slots

sequenceDiagram
    participant TF as TreeFeature
    participant RootP as RootPlacer
    participant TP as TrunkPlacer
    participant FolP as FoliagePlacer
    participant TDec as TreeDecorator
    participant WGL as WorldGenLevel

    TF->>TP: getTreeHeight — two random draws
    TF->>FolP: foliageHeight, then foliageRadius — both from the UNCLIPPED height
    TF->>TF: build-height check, then getMaxFreeTreeHeight — the clearance scan
    Note over TF: clipped below the profile minimum, or none declared, abandons with nothing written
    TF->>RootP: placeRoots — false abandons the tree
    RootP->>WGL: roots, and the moss above them
    TF->>TP: placeTrunk(clipped height)
    TP->>WGL: logs
    TP-->>TF: a list of FoliageAttachments
    loop one per attachment
        TF->>FolP: createFoliage(clipped height, attachment, foliageHeight, leafRadius)
        FolP->>WGL: leaves, DISTANCE 7 as provided
    end
    TF->>TDec: place(Context) — logs, leaves and roots, each sorted by Y
    TDec->>WGL: hives, vines, podzol, propagules
    TF->>WGL: updateLeaves — a bucketed walk that rewrites every DISTANCE

Four things in that diagram are the page’s real content.

The crown is sized before the ceiling is measured. TreeFeature samples the trunk placer’s proposed height, derives FoliagePlacer.foliageHeight and FoliagePlacer.foliageRadius from that number, and only then runs the clearance scan. The scan’s answer, the clipped height, is passed on to TrunkPlacer.placeTrunk and FoliagePlacer.createFoliage — but the two crown numbers travel beside it, already decided. It is a real asymmetry that no shipped tree can express: the only species vanilla lets survive a clipping is the fancy oak, and FancyTrunkPlacer derives its cluster count from the clipped height, so a clipped fancy oak gets a smaller crown, not a bigger one. The one placer whose foliage height reads the tree height at all is SpruceFoliagePlacer, and no spruce declares a minimum clipped height, so a spruce under an overhang abandons itself instead.

Clipping usually kills the tree instead. FeatureSize.minClippedHeight is the only thing that permits a clipped tree at all, and in vanilla exactly one tree declares it: the fancy oak, at four. Every other species abandons itself the moment the scan comes back short. The scan returns two below the first blocked layer, so an obstruction at head height yields a negative number and nothing survives it.

The scan asks the trunk placer what “free” means, not the feature size. FeatureSize supplies only the radius to test. TrunkPlacer.isFree is air, anything in the replaceable-by-trees tag, or an existing log — which is how a new tree grows up through an old one — and it delegates to a virtual TrunkPlacer.validTreePos, so UpwardsBranchingTrunkPlacer quietly widens the definition with its own can grow through block set. A vine anywhere in the scanned column also fails, unless the configuration sets TreeConfiguration.ignoreVines.

Nothing rolls back. There are three places a tree can abandon itself — the build-height check, the clipped-height check, and RootPlacer.placeRoots returning false — and all three happen before a single block is written. After TrunkPlacer.placeTrunk begins there is no undo, and TreeFeature reports success even for a tree that was truncated to a stump.

The nine trunk placers

The base contract is three numbers — TrunkPlacer.getTreeHeight is a base height plus two independent random draws — and one method that writes logs and returns attachments.

typehow it differs
StraightTrunkPlacerone column, one attachment one block above the top log
ForkingTrunkPlacerleans near the top, then grows a side branch in a second random direction — and if that direction happens to equal the first, the branch is skipped and the draw is spent anyway. The main fork’s attachment carries a radius nudge of +1
GiantTrunkPlacera 2×2 column, four dirt blocks beneath it, and only the (0,0) column placed on the very top layer. Its attachment sets double trunk, as DarkOakTrunkPlacer’s does
MegaJungleTrunkPlacerthe giant trunk, plus side branches laid along a random angle every few levels, each attachment nudged −2
DarkOakTrunkPlacera leaning 2×2 trunk whose lean is two minus a draw from three, so two steps, one or none with equal chance, plus a ring of downward log stubs on a one-in-three roll per position — placed relative to the original trunk, not the leaned one. Its main attachment sits on the top log rather than above it
FancyTrunkPlacersee below
BendingTrunkPlacerrises, nudges once, then walks horizontally for a sampled bend length — and emits an attachment at every position along the whole arc, including ones where the log was not placed
UpwardsBranchingTrunkPlacera straight column that rolls a probability after each log and, on success, runs a diagonal staircase branch outward, attaching foliage at every branch log. The one placer that widens what counts as free
CherryTrunkPlacerone to three branches that random-walk toward a computed endpoint, choosing vertical or horizontal per step by the remaining ratio, with the log axis rotated sideways for the horizontal runs. It derives a fourth branch-height provider in its constructor that no codec ever sees, which is why the codec insists the declared range spans at least two blocks

FancyTrunkPlacer is the one worth watching, because it is the only placer that plans before it writes. It works out a crown position per level from a circle equation, then walks the line from trunk to crown twice: once with placement switched off, purely to ask whether every block on the way is free, and again for real only if it was. A branch whose base has slid below the trunk top is clamped, which is the whole “branches slope down as they go out” look. And it carries one computation that cannot do anything:

One — crown candidates a fancy oak tries per level, always, because the count is a minimum against one, over an expression that is never below one (FancyTrunkPlacer). The named density constant it multiplies has no effect on any tree of any height.

The eleven foliage placers

A foliage placer gets one attachment and three numbers — a height, a radius, and an offset it samples itself — and its two real degrees of freedom are how the radius changes with height and which positions inside a row it skips.

typehow it differs
BlobFoliagePlacerthe plain oak blob: radius tapers by half the row index, corners clipped on a coin flip and always clipped on the row at y = 0
FancyFoliagePlacerthe blob’s subclass, but the skip test is a genuine circle rather than a corner roll
BushFoliagePlacerthe blob with a much steeper taper — the full row index, not half of it
SpruceFoliagePlacerthe saw-tooth: a radius that grows a block per row and resets to nothing whenever it reaches a ceiling that is itself climbing. The only placer whose row loop is bounded by the foliage height alone rather than the offset
PineFoliagePlacerone cone, and the only placer that overrides FoliagePlacer.foliageRadius — it adds a draw scaled by the trunk height on top of the configured radius
AcaciaFoliagePlacernot a loop at all: three explicit rows, with a cross cut through the flat plate. Its declared foliage height is a constant zero
DarkOakFoliagePlacertwo explicit rows, or three or four when the trunk is 2×2, wider with it, and the only placer that overrides the signed skip test — it removes the four true corners of the widest row before the signed-to-absolute fold can hide them
MegaJungleFoliagePlacerregistered as jungle_foliage_placer, not mega_jungle. A circle plus a hard Manhattan cap that skips anything seven or more blocks out
MegaPineFoliagePlacerthe only one that iterates absolute world Y, so it can make its taper jagged by widening every other row
RandomSpreadFoliagePlacernever places a row. It fires a configured number of shots at a box, each coordinate the difference of two draws, so the leaves cluster toward the attachment and thin out. Its skip test is unreachable dead code, and it ignores the offset the base class sampled for it
CherryFoliagePlacertwo narrowing cap rows, a stack of full-radius rows, then the only two uses of the hanging-leaves row helper. It punches probabilistic holes: an edge hole on the bottom row, and on wide rows an unconditional corner removal plus a probabilistic diagonal band

Two of the contract’s parameters are dead in all eleven implementations: FoliagePlacer.createFoliage’s tree height, and the TreeConfiguration that FoliagePlacer.foliageHeight receives. Nobody reads either.

Roots, and the tree that plants itself by failing

RootPlacerType registers one type. MangroveRootPlacer is the only root placer in the game, and the base class exists for it: RootPlacer.trunkOffsetY is what lifts a mangrove’s trunk one to three blocks clear of the mud, and AboveRootPlacement is the moss carpet that lands on top of a root.

It simulates the whole root system before writing anything. Starting from the trunk position it recurses outward in each of the four horizontal directions; each step’s candidates are straight down, sideways, or both, depending on how far out the walk already is and a skew roll. The termination rule reads backwards: the recursion returns true only when it has run out of placeable candidates, and reaching the maximum root length returns false — which propagates all the way out and abandons the tree. A mangrove is therefore planted only where its roots find solid ground before they run out of length.

One asymmetry inside it: a root that lands in mud is written from the muddy provider instead, and that branch skips the base implementation entirely — so muddy mangrove roots never get their moss carpet.

The decorators, and the pass that undoes half of them

TreeFeature accumulates four sets as it writes — roots, logs, leaves and decorations — and hands the first three to each TreeDecorator as a TreeDecorator.Context, which sorts all three ascending by Y. That sort is the reason four different decorators can say “the lowest log” and mean it. A decorator returns nothing, so one that finds no valid spot is indistinguishable from one that succeeded.

Ten types, in four groups. The ones that hang things off the tree are TrunkVineDecorator and LeaveVineDecorator (vine curtains), PaleMossDecorator, CocoaDecorator and AttachedToLeavesDecorator — that last one blacklists an exclusion box around each placement so the propagules cannot crowd each other. The one that changes a block already placed is CreakingHeartDecorator, which shuffles the tree’s logs and converts one that is completely surrounded by other logs — a random such log, not the first. BeehiveDecorator looks like a third but is not: it hangs its nest in an air block beside a log, and populates the block entity with two or three bees on the spot (block entities). The ones that write on the ground around the tree are AlterGroundDecorator (the podzol discs under a mega spruce, which reach several blocks beyond the trunk) and PlaceOnGroundDecorator (leaf litter, over an inflated box). And AttachedToLogsDecorator is used by FallenTreeFeature rather than by trees.

Then the last step, and it is the one that reaches furthest. TreeFeature.updateLeaves runs a bucketed breadth-first walk out from the log set and rewrites BlockStateProperties.DISTANCE on every block in the tree’s bounding box that has that property. Three consequences follow, and all three are visible in game. A neighbouring tree’s leaves caught inside the box get rewritten too. Blocks in the prevents-nearby-decay tag report distance zero and act as extra roots for the walk. And the decoration and root sets are marked as occupied before the walk starts, so a decorator-placed block or a mangrove root blocks leaf-distance propagation through itself. Anything the walk cannot reach within six steps keeps the BlockStateProperties.DISTANCE of 7 the foliage provider gave it — already decaying — and falls apart on its first random tick.

Five species, side by side

trunkfoliagerootsclearancedecorators
oakstraight, 4 + two drawsblob, radius 2two layers
fancy oakfancy, base 3fancy, radius 2, offset 4two layers, min clipped 4
dark oakdark oak, 6 + drawsdark oak, radius 0 — all the width is hardcoded in the placerthree layers— (pale oak adds moss, and a creaking heart)
cherrycherry, 7, one to three branchescherry, radius 4, four hole probabilitiestwo layers— (a 5% bee-nest variant exists)
mangroveupwards branching, per-log branch probabilityrandom spread, 70 shotsmangrove, trunk lifted 1–3two layersvines, propagules, a 1% bee nest

The columns nobody expects to matter are where the personality lives: dark oak’s configured leaf radius is zero, and mangrove’s foliage placer is the one that does not place rows.

Questions players ask

Why does a bone-mealed oak sometimes come out enormous? Because TreeGrower picks between two configured features on a probability — a plain oak most of the time, a fancy oak one time in ten — and separately checks for a flower within a 5×3×5 box, which swaps in the bee-nest variants. The same mechanism is why a spruce sapling grown as a 2×2 is a mega pine half the time rather than a mega spruce.

Why does a player-grown pale oak have no creaking heart? TreeGrower.PALE_OAK’s mega tree is the bone-meal variant of the configured feature, which is the one with no decorators on it at all. The moss and the heart only arrive on a worldgen pale oak.

Why does a mangrove propagule grow underwater? Sapling growth is the other entry into this machine and it hand-manages the sapling block: for the single-sapling path it replaces the sapling with whatever the fluid there would be, so a waterlogged propagule grows into water. The 2×2 path is stranger — it clears all four saplings with no-update writes and puts them back if the feature fails.

Do leaves know which tree they came from? No. Nothing in the placed tree records its species; a leaf’s only per-block state is BlockStateProperties.DISTANCE and BlockStateProperties.WATERLOGGED, the first written by the feature’s own breadth-first pass and the second taken from what was already in the world. A log carries a BlockStateProperties.AXIS the trunk placer chooses, and that is the whole of it. FoliagePlacer.tryPlaceLeaf also refuses to overwrite a leaf a player placed, by testing the persistent flag.

Is any of this shared with the rest of decoration? The clearance idea, no — it is TreeFeature’s alone. But the four setter consumers, the sets they fill and the final shape update are the same machinery StructureTemplate uses to fix block shapes at the edge of a placed structure, and every block a tree writes goes in with the same flags: update neighbours, update clients, and known shape (blocks and states).

Where to look

TreeFeature.place · TreeConfiguration · TreeConfiguration.TreeConfigurationBuilder · TrunkPlacer.placeTrunk · TrunkPlacer.getTreeHeight · TrunkPlacer.isFree · FancyTrunkPlacer · CherryTrunkPlacer · UpwardsBranchingTrunkPlacer · FoliagePlacer.createFoliage · FoliagePlacer.shouldSkipLocation · FoliagePlacer.FoliageAttachment · FoliagePlacer.tryPlaceLeaf · RandomSpreadFoliagePlacer · DarkOakFoliagePlacer · MangroveRootPlacer.placeRoots · MangroveRootPlacement · FeatureSize.getSizeAtHeight · TwoLayersFeatureSize · ThreeLayersFeatureSize · TreeDecorator.Context · BeehiveDecorator · AlterGroundDecorator · TreeFeatures · TreeGrower.growTree · SaplingBlock.advanceTree


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