Changes the way Minecraft stores region (mca) files, leading to massive reductions in file size.
World/Region Optimizer
A Minecraft 1.12 Paper plugin that losslessly compresses and repacks region (.mca) files, reducing world folder sizes by 40–60% with near-zero impact on gameplay, builds, entities, or world data.
Table of Contents
- Overview
- How It Works
- Safety Guarantees
- Installation
- Commands
- Configuration
- Building from Source
- Technical Reference
- FAQ
- Requirements
Overview
Minecraft stores its world data in Anvil region files (.mca). Each file contains up to 1,024 chunks compressed individually with zlib at the lowest compression level (level 1 — fastest). Over time these files accumulate wasted space from:
- Low-effort compression (Minecraft prioritises write speed over size)
- Empty chunk sections that were never cleaned up (all-air 16×16×16 volumes stored explicitly)
- Redundant cached data (height maps that Minecraft recalculates anyway)
- Sector fragmentation gaps left by incremental chunk updates
RegionOptimizer processes your world files offline (or while idle) and addresses all four sources of waste without touching a single byte of actual world content.
How It Works
Optimization Layers
All four optimizations are completely lossless — Minecraft reads the output files identically to unoptimized ones.
1 — Zlib Recompression ~Up to 20–30% savings
Minecraft writes chunk data at Deflate level 1 (fastest, worst compression). RegionOptimizer recompresses every chunk at Deflate level 9 (maximum compression), producing significantly smaller output in the same format. Minecraft reads both identically — only the compressed bytes change.
2 — Empty Section Pruning ~Up to 5–20% savings
A Minecraft chunk is divided vertically into 16 sections (each 16×16×16 blocks). The upper sections of most chunks — sky, void, unexplored heights — are entirely air. Minecraft stores these explicitly as arrays of 4,096 zero-bytes, even though the Anvil format specification guarantees that a missing section is treated as all-air.
RegionOptimizer removes any section where:
Blocks[4096]is entirely zero (no block IDs)Add[2048]is absent or entirely zero (no extended block IDs > 255)
This is identical to how newly generated chunks are stored — Minecraft has handled missing sections as air since the Anvil format was introduced in 1.2.
3 — HeightMap Removal ~Up to 3–5% savings
Every chunk stores a HeightMap tag: a 256-integer array (1 KB) caching the topmost solid block for each column. Minecraft always recalculates this on next chunk load when the tag is absent — it's a pure performance cache, never a source of truth. Removing it has no gameplay or visual effect.
4 — Region File Repacking ~Up to 5–10% savings
Region files store chunks in 4 KB sectors. When a chunk grows (a player builds, adds entities, etc.) and needs more sectors, Minecraft allocates new sectors at the end of the file and leaves the old location empty. These gaps accumulate over time.
RegionOptimizer rewrites every region file with tightly-packed sectors in a fresh file, then atomically replaces the original. No gaps, no wasted space.
What Is Never Changed
The following data is guaranteed to be preserved verbatim through every optimization pass:
| Data | Notes |
|---|---|
| Block IDs and Data values | Every placed block, every build, unchanged |
| Entities | All mobs, dropped items, projectiles |
| Tile Entities | Chests, furnaces, signs, command blocks, spawners |
| Biomes | Grass colour, mob spawning, rain behaviour |
| SkyLight / BlockLight | Kept to avoid lighting recalculation lag on load |
| TileTicks / LiquidTicks | Scheduled block updates (sand falling, water flowing) |
| TerrainPopulated | Prevents duplicate feature generation |
| LightPopulated | Lighting state flag |
| InhabitedTime | Time players have spent in a chunk |
Safety Guarantees
RegionOptimizer is built around a safety-first architecture:
Automatic Backups
Before touching any region file, a .mca.bak backup is created automatically (configurable). If anything fails — read error, write error, verification mismatch — the backup is restored and the original file is left intact.
Post-Write Verification
After writing an optimized region, the plugin reads it back and confirms the chunk count matches the input exactly. If even one chunk is missing, the backup is restored automatically.
Atomic File Replacement
Optimized regions are written to a .tmp_XXXXXX temporary file first. Only after the write and verification succeed is the original replaced — using an atomic filesystem move where supported, with a safe fallback on unsupported filesystems. A crash or power loss during the write cannot corrupt the original.
Loaded-Chunk Safety Check
Before processing each region, the plugin queries the main thread to check whether any of the 1,024 chunks in that region are currently loaded by the server. If any are loaded, the region is skipped. This prevents races between the optimizer's atomic file-replace and Minecraft's own chunk-save writes.
Idle-Time Guard
Regions modified within the last N minutes (configurable, default: 5) are skipped automatically, providing a secondary defence against active-server writes.
Per-Chunk Error Isolation
Each chunk is validated individually before reading (bounds checks on sector offsets, data lengths, and file EOF). Invalid or truncated entries are skipped with a warning — a single corrupt chunk entry never aborts the entire region.
Chunk-Level Fallback
During NBT optimization, if anything goes wrong decompressing, parsing, or recompressing a single chunk, that chunk is returned unchanged (original compressed bytes preserved). No chunk is ever corrupted by an optimizer error.
Installation
- Make sure you are running Paper 1.12+ (or a compatible fork).
- Drop
region-optimizer-1.0.0.jarinto your server'splugins/folder. - Start or restart the server. A default
config.ymlis created automatically. - Use the commands below to run your first optimization.
Recommended: Run optimizations while no players are online, or stop the server, optimize offline, and restart. The plugin has safeguards for live servers, but offline operation is always the safest approach.
Commands
Main command: /regionoptimize — Aliases: /ro, /regionopt
Permission node: regionoptimizer.use (default: op)
/regionoptimize world <worldname> [--no-backup]
Optimizes every .mca file in the given world's region/ folder.
/regionoptimize world world
/regionoptimize world world_nether
/regionoptimize world world_the_end --no-backup
- Runs fully asynchronously — the main thread is never blocked.
- Progress is broadcast to all players with
regionoptimizer.usepermission at a configurable interval. - If
--no-backupis passed,.mca.bakfiles are not created. Only use this if you have your own backup solution. - Regions with loaded chunks or recent writes are automatically skipped and reported.
- A detailed final report is broadcast when complete.
Example final report:
✔ Optimization complete for world 'world'!
Regions : 312 processed, 0 failed
Chunks : 89,341 optimized, 6,247 unchanged
Size : 4.82 GB → 2.14 GB (55.6% reduction, 2.68 GB saved)
Empty sections removed : 1,204,816
HeightMaps removed : 89,341
Time elapsed : 16m 12s
/regionoptimize status
Shows the progress of the currently-running optimization task.
/regionoptimize status
Output example:
[RegionOptimizer] Running: Processing region 147/312: r.4.-2.mca (38.2% size reduction so far)
/regionoptimize stats [worldname]
Shows the disk usage of region files for a world (or all loaded worlds if no name is given). Also reports any backup files still on disk.
/regionoptimize stats
/regionoptimize stats world
/regionoptimize stats world_nether
Output example:
[RegionOptimizer] world: 312 regions, 4.82 GB total
[RegionOptimizer] world_nether: 48 regions, 312.4 MB total (+12 backups using 312.4 MB)
/regionoptimize cancel
Requests cancellation of the running optimization task. The current region finishes before stopping; partial results up to that point are kept.
/regionoptimize cancel
Configuration
plugins/RegionOptimizer/config.yml:
optimization:
# Zlib compression level for chunk recompression.
# Minecraft default is 1 (fastest). Level 9 (maximum) gives the best compression
# at the cost of more CPU time during the one-time optimization pass.
# This only affects the optimization run — normal gameplay uses Minecraft's own level.
# Range: 1–9 | Default: 9
compression-level: 9
# Remove empty (all-air) chunk sections from NBT data.
# Minecraft treats a missing section as all-air — this is guaranteed Anvil behaviour.
# Default: true
remove-empty-sections: true
# Remove the HeightMap array from each chunk.
# HeightMap (256 ints = 1 KB/chunk) is always recalculated by Minecraft on next load.
# Default: true
remove-heightmap: true
# Repack region files to eliminate sector fragmentation gaps.
# Default: true
repack-region: true
# Create a .mca.bak backup of each region file before optimizing.
# STRONGLY recommended. Disable only if you have external backups.
# Default: true
create-backups: true
# Delete backup files after each region is successfully verified.
# Default: false (keep backups until manually removed)
delete-backups-after-success: false
# Progress broadcast interval in seconds.
# Default: 10
progress-interval-seconds: 10
# Maximum number of region files processed simultaneously.
# Higher values use more CPU and memory. Recommended: 1–4.
# Default: 2
parallel-regions: 2
# Skip regions modified within this many minutes of the optimization run.
# Provides a secondary guard against races with active server writes.
# Set to 0 to disable (not recommended on live servers).
# Default: 5
min-idle-minutes: 5
Building from Source
Requirements: Java 8+, Maven 3.6+
git clone https://github.com/yourorg/region-optimizer.git
cd region-optimizer
mvn clean package
The built jar is at target/region-optimizer-1.0.0.jar.
To run the test suite (no server required):
mvn clean compile test-compile
javac -cp target/classes -d target/test-classes \
src/test/java/com/lifestealmc/regionoptimizer/RegionOptimizerTest.java
java -cp target/classes:target/test-classes \
com.lifestealmc.regionoptimizer.RegionOptimizerTest
The test suite runs 450 checks covering:
- NBT binary write/read round-trip for all tag types
- Empty section detection and removal
- HeightMap removal
- Compression level improvement verification
- Non-empty section and entity/biome/TileEntity preservation
- MCA write → read round-trip with 100 chunks
- Negative region coordinate filename parsing (
r.-1.-2.mca) - Corrupt/truncated chunk entry handling
- Full pipeline data integrity with 50 chunks, 200 section removals, 50 heightmap removals
Technical Reference
MCA File Format
Byte offset Size Description
───────────────────────────────────────────────────────
0 4,096 Location table — 1,024 × 4 bytes
Bits 31–8 : sector offset (big-endian 3-byte uint)
Bits 7–0 : sector count (1-byte uint, max 255)
4,096 4,096 Timestamp table — 1,024 × 4-byte unix timestamps
8,192+ N Chunk data blocks, each padded to 4,096-byte boundaries
4 bytes : payload length (includes compression type byte)
1 byte : compression type (1 = gzip, 2 = zlib)
N bytes : compressed NBT data
───────────────────────────────────────────────────────
Sector size = 4,096 bytes
Header size = 8,192 bytes (2 sectors)
Max sectors = 16,777,215 (3-byte offset field)
Max chunk sz = 1,044,480 bytes (255 sectors × 4,096)
Absent chunk: offset == 0 AND count == 0 in the location table.
Chunk index : (localX & 31) + (localZ & 31) * 32
NBT Chunk Structure
The relevant tags in a 1.12 chunk compound (simplified):
TAG_Compound ("")
TAG_Compound ("Level")
TAG_Int "xPos" chunk X coordinate
TAG_Int "zPos" chunk Z coordinate
TAG_Long "LastUpdate" last tick this chunk was saved
TAG_Long "InhabitedTime" cumulative player-presence ticks
TAG_Byte "TerrainPopulated" whether terrain features have been placed
TAG_Byte "LightPopulated" whether lighting has been calculated
TAG_Byte_Array "Biomes" [256] biome IDs — ONE PER COLUMN
TAG_Int_Array "HeightMap" [256] highest solid Y per column ← REMOVED
TAG_List "Sections" list of up to 16 section compounds
TAG_Compound
TAG_Byte "Y" section index 0–15
TAG_Byte_Array "Blocks" [4096] block IDs ← CHECKED FOR AIR
TAG_Byte_Array "Add" [2048] high block ID nibbles (optional) ← CHECKED
TAG_Byte_Array "Data" [2048] block data nibbles
TAG_Byte_Array "BlockLight" [2048] block light nibbles
TAG_Byte_Array "SkyLight" [2048] sky light nibbles
TAG_List "Entities" entity compounds ← PRESERVED
TAG_List "TileEntities" tile entity compounds ← PRESERVED
TAG_List "TileTicks" scheduled block ticks ← PRESERVED
Tags marked ← REMOVED are stripped during optimization.
Tags marked ← CHECKED are inspected to confirm the section is all-air before removal.
Tags marked ← PRESERVED are never read, modified, or removed.
Architecture
com.lifestealmc.regionoptimizer
│
├── RegionOptimizerPlugin.java Plugin entry point, lifecycle management
│
├── command/
│ └── OptimizeCommand.java /regionoptimize handler + tab completion
│
├── nbt/ Self-contained NBT read/write library
│ ├── NBTBase.java Abstract base with tag type constants
│ ├── NBTTagByte/Short/Int/... Primitive tag implementations
│ ├── NBTTagByteArray.java Byte array tag (includes isAllZero() helper)
│ ├── NBTTagIntArray.java Int array tag
│ ├── NBTTagString.java UTF-8 string tag
│ ├── NBTTagList.java Homogeneous ordered list
│ ├── NBTTagCompound.java Key-value map with typed getters/setters
│ ├── NBTReader.java Binary NBT deserialiser (stream → object tree)
│ └── NBTWriter.java Binary NBT serialiser (object tree → stream)
│
├── region/
│ ├── ChunkEntry.java Immutable value type: coordinates + compressed bytes
│ └── MCARegion.java MCA read/write with bounds checking and atomic replace
│
├── optimizer/
│ ├── ChunkOptimizer.java Per-chunk NBT optimisation + recompression
│ │ └── ChunkOptimizeResult Result type: entry + sections removed + heightmap flag
│ ├── RegionOptimizer.java Per-region pipeline: backup → optimize → verify → stats
│ └── OptimizationStats.java Thread-safe AtomicLong counters for all metrics
│
└── task/
└── WorldOptimizeTask.java Async Runnable: iterates regions, checks safety,
reports progress, handles parallel execution
Threading model:
- All file I/O and NBT processing runs on a dedicated async thread pool — the Bukkit main thread is never blocked.
- The loaded-chunk safety check (
world.isChunkLoaded) is dispatched to the main thread viacallSyncMethodwith a 10-second timeout. OptimizationStatsusesAtomicLongfor all counters — safe for parallel region processing.- Progress reporting uses
runTaskto deliver messages on the main thread.
FAQ
Will this corrupt my world?
No. The plugin only modifies how data is compressed and stored — not the data itself. Builds, entities, and gameplay state are preserved exactly. Post-write verification confirms every chunk survived before the original file is replaced. If verification fails, the original is restored from the backup automatically.
Can I run this while players are online?
Technically yes, but offline is always safest. The loaded-chunk check and idle-time guard provide protection on live servers, but they have inherent race windows. For the best result, run /save-all, allow any active regions to go idle, then optimize. Even better: stop the server, optimize, restart.
How long does it take?
On typical hardware with parallel-regions: 2:
- Small world (~50 regions): 1–2 minutes
- Medium world (~300 regions): 5–15 minutes
- Large world (1,000+ regions): 30–60 minutes
CPU is the bottleneck (level-9 compression is ~5× slower than level-1). Disk I/O is typically not a bottleneck since each region fits in L3 cache.
What if the server crashes during optimization?
Each region is written to a .tmp_XXXXXX file and renamed atomically only after verification. The worst case is that the current region's .tmp file is left on disk — it can be safely deleted. The original .mca and its .mca.bak are untouched until the rename succeeds.
Can I optimize again after new terrain is generated?
Yes. Each optimization run is independent. New regions written by Minecraft will be at level 1 compression — you can run the optimizer again at any time to reclaim space on those regions. Already-optimized regions will see smaller savings on repeat runs (compression cannot improve beyond level 9).
Does this affect Nether and End worlds?
Yes — any world with a region/ folder can be optimized. Use the world name exactly as it appears in your server (e.g., world_nether, world_the_end).
What about backups taking up double the space?
Set delete-backups-after-success: true in config.yml to delete backups automatically after each region is successfully verified. Or set create-backups: false if you manage your own external backups (e.g., nightly snapshots).
What is the Add array in a chunk section?
In Minecraft 1.12, block IDs are stored as one byte per block in the Blocks array. For modded block IDs > 255, a second Add nibble array extends the IDs to 12 bits. When checking whether a section is all-air, the plugin checks both arrays — a section is only removed if both Blocks and Add are entirely zero.
Requirements
| Requirement | Version |
|---|---|
| Minecraft server | 1.12.2 |
| Server software | Paper (or compatible fork) |
| Java | 8 or higher |
| Permissions plugin | Optional (ops have access by default) |
This plugin targets the 1.12 Anvil chunk format specifically. It will not run on 1.13+ servers, which use a different chunk format (Data Version, Palette, etc.).
Information
| Category | World Management |
|---|---|
| Published on | June 12, 2026 |
| License | Apache 2.0 |
| Downloads | 0 |
| Stars | 1 |
| Watchers | 1 |