Migration Guide
What changes when you upgrade, and what the plugin converts for you.
Upgrading from v3 to v4
4.0.0 is a major release. The plugin migrates its data, config.yml, and messages.yml automatically on the first server start. A working server needs no manual steps. The breaking changes are the runtime requirements (Java and Minecraft versions) and the buildsystem-api surface for dependent plugins.
Read the sections below for the full picture: requirements, automatic conversion, configuration, permissions, commands, language, and API.
Back up first. Stop the server and copy the plugins/BuildSystem/ folder (and any external world saves) before upgrading. The conversion is one-way: once worlds, statuses, and visibilities are written in the v4 format, an older build can no longer read them.
1. System Requirements
These are hard requirements. The plugin will not start otherwise.
Java
21
25
Minecraft
1.21+
26.1+
Server software
Spigot / Paper
Spigot / Paper
Upgrade your server's JDK to 25 before dropping in the new jar.
2. Automatic Data & File Conversion
On the first 4.0.0 boot the plugin upgrades each legacy structure in place. Nothing is lost; unrecognized values fall back to a sensible default.
Storage keying
worlds.yml / folders.yml keyed by world/folder name
Keyed by a stable UUID, with the name stored as a field
A rename is now a field update, not a key move, so renames no longer leave orphaned entries. Folder parent references switch from the parent's name to its UUID.
World status
IN_PROGRESS (uppercase enum)
in_progress (lowercase registry id)
Unrecognized values default to not_started.
World visibility
private: true / false
visibility: ADDED_PLAYERS / EVERYONE
One-to-one mapping.
Folder category
category: PUBLIC (uppercase enum)
category: public (lowercase registry id)
Unrecognized values default to public.
Status display names
status_in_progress: "&6In Progress" in messages.yml
displayName + color in statuses.yml
Seeded on first boot only if statuses.yml does not exist yet. The leading colour code is split into color; the remainder becomes displayName, preserving any rename/translation.
config.yml / messages.yml
Older layouts
Versioned, auto-migrated
Missing keys are appended and some keys are relocated; existing customisations are preserved.
Before re-keying, the plugin writes a one-time backup next to each affected file:
plugins/BuildSystem/worlds.yml.v3.bakplugins/BuildSystem/folders.yml.v3.bak
The migration also creates two new files for the now-customizable statuses and categories:
plugins/BuildSystem/statuses.ymlplugins/BuildSystem/categories.yml
3. Configuration File Changes
config.yml is versioned and migrated on load, so custom values survive the upgrade. Two flags are gone and one is new.
Removed Keys
Silently ignored in v4, safe to delete from config.yml:
settings.per-option-permissions, superseded by the always-onbuildsystem.setting.<option>nodes.settings.restrict-template-access, superseded by the deny-to-restrictbuildsystem.create.template.<name>model.
Replaced Keys
world.disabled-physics.*becomesworld.defaults.physics-exceptions.*. The meaning is inverted along with the name: the oldprevent-x: falsebecomesx: true, read as "this behavior still runs while physics are off". Existing configs are converted on load, so no manual edit is needed.world.max-amount.public/world.max-amount.privatebecomeworld.limits.public/world.limits.private. Values carry over on load. The meaning changed too: the limit is now the default number of worlds one player may create of that visibility, applied only to players who hold nobuildsystem.create.<visibility>.<amount>node of their own, and ignored entirely forbuildsystem.admin. Previously the value was compared against the total number of worlds on the server, so setting either key stopped all world creation once the server reached that many worlds. If you set these in v3 expecting a per-player cap, that is what you now get; if you were relying on the old behavior as a server-wide cap, there is no direct replacement.
New Keys
settings.world-permission-whitelist(default:[]) restricts the permission strings/worlds setPermissionmay assign. An empty list allows any value.world.void-block.enabled(default:true) andworld.void-block.material(default:GOLD_BLOCK) control the block placed at the spawn of a newly generated void world so the first player to join does not fall.world.defaults.physics-exceptions.<category>(default:falsefor all nine categories) seeds new worlds, and any existing world that has no stored value yet.
Changed Defaults
world.unload.enabled now defaults to true. The shipped config.yml has always said true, but the plugin fell back to false when the key was missing, so a config written before the key existed never auto-unloaded anything. After upgrading, those servers start unloading empty worlds on the world.unload.time-until-unload schedule. Set it to false explicitly if you want the old behavior, and check world.unload.blacklisted-worlds.
4. Permission Changes
Existing command permissions are unchanged. v4 adds the following.
New Static Nodes
buildsystem.savetemplate
OP
Run /worlds saveTemplate to copy a live world into templates/.
buildsystem.edit.pin
OP
Pin worlds to the top of navigator lists via the Edit GUI.
buildsystem.edit.icon
OP
Set a world's custom icon material or skull texture in the Edit GUI.
buildsystem.edit.physics
OP
Open the physics exceptions menu by right-clicking the physics toggle in the Edit GUI.
buildsystem.backup
OP
View a world's backup history GUI.
buildsystem.backup.create
OP
Force a manual backup with /worlds backup create.
buildsystem.setup
OP
Open /setup to manage statuses, categories, and default world icons.
New Dynamic Nodes
Resolved at runtime from the id of the status, category, template, or generator:
buildsystem.create.category.<categoryId>
OP
Create a world in a specific category (e.g. buildsystem.create.category.public). Admins with the create bypass may create in any category.
buildsystem.navigator.category.<categoryId>
true
Show the category in the navigator and enable its /worlds <category> shortcut. Deny it to hide a category and disable its command together.
buildsystem.setstatus.<statusId>
inherits buildsystem.setstatus (OP)
Assign a status to a world. Built-in statuses strip underscores for backwards compatibility (in_progress → …inprogress); custom statuses use their full id (my_status → …my_status).
buildsystem.create.template.<name>
true
Use a specific template. Deny the node to restrict it.
buildsystem.create.generator.<name>
true
Use a specific chunk generator. Deny the node to restrict it.
buildsystem.setting.<option>
true
Toggle a /settings option (e.g. buildsystem.setting.no-clip). Deny to restrict.
Removed Nodes
buildsystem.create.private
buildsystem.create.category.private
If your setup grants buildsystem.create.private, switch to the per-category node above. This is not the same as the buildsystem.create.private.<amount> world-limit node, which is unchanged.
5. Navigator Command Changes
The fixed /worlds archive, /worlds private, and /worlds public subcommands are replaced by per-category /worlds <category> shortcuts, generated from the navigator layout instead of being hardcoded.
Existing setups keep working: the three built-in categories still answer to archive, private, and public. A real subcommand always wins a name clash, so a custom category named like a command can never shadow it.
6. Language File (messages.yml) Changes
Default English: delete
messages.ymland restart to regenerate a fresh file with every v4 key.Custom translations: keep the file; the plugin appends new keys on startup. Localize the newly added keys manually.
Obsolete Keys (can be removed)
No longer read. Their values were copied into statuses.yml on the first boot:
status_not_started,status_in_progress,status_almost_finished,status_finished,status_archive,status_hidden
Changed Keys
world_navigator_create_world, default changed to&bCreate a Worldbecause clicking the item now opens a category picker.night_unknown_worldis finally used./nightin an unknown world used to send the/daymessage by mistake, so if you translated onlyday_unknown_world, translate this one too.
Keys No Longer Used
The navigator rewrite left these behind. Nothing reads them, so they can be deleted from a translated messages.yml:
old_navigator_world_navigator, old_navigator_world_archive, old_navigator_private_worlds, new_navigator_world_navigator, new_navigator_world_archive, new_navigator_private_worlds, world_navigator_title, archive_title, archive_no_worlds, private_title, private_no_worlds, private_create_world, worldsettings_gamerule_, worldeditor_time_lore_unknown, explosions_deactivated_in_world, noai_activated_in_world
New Keys
Added in 4.0 and appended automatically. Translate them if you keep a custom file:
worlds_importall_world_failed, shown per world when a bulk import fails.worlds_import_unknown_type, shown when/worlds import -tgets a value that is not a world type.worlds_template_creation_error, shown when a template cannot be copied.
7. Developer API Changes
Plugins compiling against buildsystem-api must recompile against 4.0.0 and apply the changes below.
Breaking Changes
Event package
de.eintosti.buildsystem.api.event.BuildWorldManipulationEvent
Import from de.eintosti.buildsystem.api.event.world instead.
World creation
WorldService.createWorld(...)
Fluent builder: worldService.newWorld(name)…build().
World importing
WorldService.importWorld(name, ...)
Fluent builder: worldService.importWorld(name)…build().
World data
Type<T> objects with a getter/setter per setting
Typed keys: data.get(WorldDataKey.PHYSICS) / data.set(WorldDataKey.PHYSICS, false). The built-in keys live on WorldDataKey.
Icons
XSeries XMaterial / Profileable in signatures
Bukkit Material everywhere; head textures are plain strings via Displayable.getIconSkullTexture(). XSeries is no longer a dependency of the API artifact.
Bukkit handle
BuildWorld.getWorld() returned a nullable World
Now returns Optional<org.bukkit.World>.
World status
BuildWorldStatus (enum)
Now an interface resolved via BuildSystem.getStatusRegistry(). Compare by getId() / equals(), never == or switch.
Categories
NavigatorCategory (enum)
Now an interface resolved via BuildSystem.getNavigatorCategoryRegistry(); Folder.getCategory() returns it. Compare by getId() / equals(), never == or switch.
Visibility
isPrivateWorld() / setPrivateWorld(boolean)
Replaced by WorldData.getVisibility() / setVisibility(Visibility) (EVERYONE / ADDED_PLAYERS).
API lookup
BuildSystemProvider.register() / unregister()
No longer public. Resolve via BuildSystemProvider.get() or Bukkit's ServicesManager.
Removed
BuildWorld.setLoaded(boolean). The read-onlyisLoaded()remains.BuildWorld.asProfileable(). Head textures are read throughDisplayable.getIconSkullTexture().WorldData.getConfigFormat()andWorldData.getAllData().The public registration entrypoints on
BuildSystemProvider(see above).
New (non-breaking)
Available to dependent plugins but not required for an upgrade:
World lifecycle events in
event.world(BuildWorldCreateEvent,BuildWorldPostCreateEvent,BuildWorldDeleteEvent,BuildWorldPostDeleteEvent,BuildWorldRenameEvent,BuildWorldUnimportEvent,BuildWorldStatusChangeEvent), plus backup and folder events inevent.backupandevent.folder.WorldService.importWorlds()for bulk import spread across ticks.WorldStatusRegistryandNavigatorCategoryRegistry, exposed viaBuildSystem.getStatusRegistry()/getNavigatorCategoryRegistry().Displayable.getIconSkullTexture()for custom head icons.PhysicsCategoryinapi.world.data, the behaviours covered by per-world physics exceptions.
Last updated