The command failed because /fill replaces only the block types you specify, and air is not dirt. Your syntax fills only existing dirt blocks inside that 41×1×41 region with dirt, which changes nothing; air stays air. To swap air for dirt, you must either state air explicitly (/fill ~-20 ~ ~20 ~20 ~ ~20 dirt replace air) or, on Java Edition, omit the replace clause entirely so every block in the volume, air included, becomes dirt.
What /fill Actually Does With the replace Argument
The /fill command has two operating modes, and the difference explains nearly every failed attempt. In its basic form, /fill overwrites every block in the volume with the target block. Add the keyword replace and the command behaves like a selective mask: /fill replace [filter] changes only blocks matching the filter.
The mechanics sit in how the game evaluates the filter. With no filter after replace, the game treats it as "replace everything," identical to the basic form. With a filter, the game iterates the volume coordinates and performs a block-state comparison. A dirt target requires the existing block to be dirt. An air block fails that comparison, so the coordinate is skipped.
Your command, /fill ~-20 ~ ~20 ~20 ~ ~20 dirt replace air, should work in principle because it names air as the filter. When it does not, the cause is almost always edition, version, or permission — covered below.
Plain language: replace is a search-and-swap, not a paint bucket.
Coordinates, Volume Limits, and the Numbers Behind a Silent Failure
Relative coordinates (~) resolve from the command's execution position, not the world origin. The tilde offsets are added to your x, y, and z. Because your y offsets are both ~, the volume is exactly one block tall. That yields a floor of 41 × 1 × 41 = 1,681 blocks.
Two hard ceilings matter. Java Edition caps /fill at 32,768 blocks per command. Bedrock Edition caps it at 32,768 as well but enforces different defaults for command blocks. At 1,681 blocks you are far under both, so volume is not your problem.
A one-block-tall slab is the most common self-inflicted error. Players expect a cube and get a pancake. If your intent was a 41×41×41 volume, the correct form is /fill ~-20 ~-20 ~20 ~20 ~20 ~20 dirt replace air. That is 68,921 blocks — over the Java limit — so it must be split into chunks or run from a command block with a higher limit setting.
Red flag: running the command from a position where ~ resolves differently (inside a command block, at a world spawn anchor, or after a teleport) shifts the entire region. Red flag: negative offsets without spaces (for example ~- 20) parse as an error, not an offset. Red flag: a stale datapack or behavior pack overriding /fill silently. Red flag: cheats disabled on a world, which makes /fill fail with a permissions message. Red flag: Bedrock's "Activate Cheats" toggle being off in a realm.
Edition and Version Differences That Break the Syntax
Java Edition accepts replace with an optional filter after it. Bedrock Edition historically required the filter bracket syntax in some versions: /fill ~-20 ~ ~20 ~20 ~ ~20 dirt replace air is valid in modern Bedrock, but older builds used replace [air] with square brackets, and mixing the two throws a syntax error.
Java added block-state arguments with 1.13, so a filter can include states like replace air["block_light"]=0. Bedrock does not support that level of state filtering in /fill. If you copy a Java command into Bedrock, the state syntax will fail.
The optional "destroy" and "hollow" modes are separate keywords, not filters. /fill ... dirt replace air destroy would additionally drop items and play break effects. On many servers, operators disable destroy for performance, and the command then errors rather than silently ignoring it.
Comparison: Which Command Fits Which Job
| Goal | Command form | Edition support | Notes |
|---|---|---|---|
| Convert only air to dirt | /fill ~-20 ~ ~20 ~20 ~ ~20 dirt replace air | Java and Bedrock | State list safe in both |
| Overwrite all blocks to dirt | /fill ~-20 ~ ~20 ~20 ~ ~20 dirt | Both | Air becomes dirt too |
| Replace dirt with stone | /fill ~-20 ~ ~20 ~20 ~ ~20 stone replace dirt | Both | Standard mask use |
| Replace air, dropping items | /fill ... dirt replace air destroy | Both | Often disabled on servers |
| Hollow shell | /fill ... dirt hollow | Both | Interior becomes air |
| Large radius beyond limit | Split into multiple commands or use /fillbiome for biomes | Java | /fillbiome does not place blocks |
The table's core lesson: the replace filter must name the block that currently exists, not the block you want.
Counterintuitive Detail: Air Is a Real Block
Most Minecraft players think of air as absence. In the data model, air is a block with an ID (minecraft:air) and a state. Cave air and void air are distinct blocks in Java. That is why replace air works as a filter at all — the game compares block IDs, and "empty" is just another ID.
This also means replacing air inside a sealed structure works only if those coordinates are actually air. Water is not air. If your region contains water at the same y level, replace air leaves water untouched. A two-step command handles it: first replace water with dirt, then replace air with dirt, or use a single /fill with a broader filter strategy.
Practical Actions: Make the Command Work Every Time
Confirm your edition. Java: check the version in the launcher. Bedrock: check the title screen. Syntax differs for state filters and older bracket forms. Verify cheats. Type /fill without arguments; if you get a permissions error, enable cheats in world settings or the realm options. Test the volume with a single-block command. Run /setblock ~ ~ ~ dirt first. If that fails, your issue is permissions, not /fill. Check the y span. Print the coordinates with /tp or use F3. Any repeated tilde collapses the axis to one block. Run the minimal working form. /fill ~-20 ~ ~20 ~20 ~ ~20 dirt replace air. If it fails, append destroy to see the exact block error the game reports. Scale in steps. Split anything over roughly 32,000 blocks into two or three commands, each under the cap.
For a 41×41×41 cube (68,921 blocks), split the actual volume into three slabs of about 22,973 blocks each. Coordinate arithmetic is straightforward: choose three y-ranges whose ranges sum to 41. Execute from the same anchor position so ~ offsets stay consistent.
On Bedrock realms, run the command as the world owner or an operator. On Java servers, verify the operator level is at least 2, which is required for /fill.
Limitations and Edge Cases Beyond the Syntax
Permission is the most common silent blocker. Cheats disabled in a single-player world or a realm produces a generic "unknown command" or "no permission" message depending on edition, which players frequently misread as a syntax error.
Block entity conflicts are rarer but real. Replacing chests or signs with dirt removes their contents. Filters do not match block entities reliably across versions, so target by block ID only.
Chunk loading limits the effective region on servers. A /fill spanning unloaded chunks may fail or behave inconsistently. Run it near the target or force-load chunks first.
Performance matters at scale. A single 32,000-block /fill causes a tick spike. Large terrain projects should use /clone, WorldEdit on Java, or scheduled command blocks rather than one massive /fill.
Finally, the replace filter compares full block states when a state is supplied. A filter of replace dirt matches all dirt states, but replace oak_log["axis"]=y matches only vertical logs. This is powerful and a common source of "nothing happened" reports.
The command you tried was conceptually sound. The block-being-replaced logic, the one-block y span, the edition syntax, and the permission state are the four checkpoints that decide whether /fill does what you intend.