This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
BentoBox is a Bukkit/Paper library plugin (Java 21) that provides the core platform for island-style Minecraft games (SkyBlock, AcidIsland, etc.) via an extensible addon system.
./gradlew build # Build the shaded JAR
./gradlew test # Run all tests
./gradlew clean build # Clean then build
./gradlew jacocoTestReport # Generate coverage report (build/reports/jacoco/)# Run all tests in a class
./gradlew test --tests "world.bentobox.bentobox.managers.IslandsManagerTest"
# Run a specific test method
./gradlew test --tests "world.bentobox.bentobox.managers.IslandsManagerTest.testMethodName"The main plugin class is BentoBox.java (extends JavaPlugin). Almost all subsystems are accessed via singleton managers held by the plugin instance.
api/— Public API surface for addons: events, commands, panels (GUIs), user management, flags, configurationmanagers/— Core subsystems:IslandsManager,PlayersManager,AddonsManager,LocalesManager,FlagsManager,BlueprintsManager,BlueprintClipboardManager,HooksManager,PlaceholdersManager,RanksManager,CommandsManager,IslandDeletionManager,IslandChunkDeletionManager,MapManager,WebManagerdatabase/— Database abstraction supporting MongoDB, MySQL, MariaDB, PostgreSQL (via HikariCP), and SQLiteblueprints/— Island schematic handling and pastinglisteners/— Bukkit event handlers (teleport, death, join/leave, panel clicks, spawn protection)commands/— Admin and user command implementationspanels/— Inventory GUI panel systemhooks/— Integrations with external plugins (Vault, PlaceholderAPI, MythicMobs, Multiverse, LuckPerms, ItemsAdder, Slimefun, Oraxen, ZNPCsPlus, FancyNpcs, BlueMap, Dynmap, LangUtils, etc.)nms/— NMS (Native Minecraft Server) version-specific code
Islands are the central domain object. IslandsManager owns the island cache and database layer. IslandWorldManager holds per-world configuration. Protection logic is handled via FlagsManager and a rank system (RanksManager).
Addons (separate plugins) hook into BentoBox through the api/ package. They register commands, flags, events, and panels by accessing managers through BentoBox.getInstance().
Flags are the core protection/setting mechanism. There are three types:
PROTECTION— player action blocked by rank (e.g., BLOCK_BREAK)SETTING— island-level on/off toggle (e.g., ANIMAL_SPAWNING)WORLD_SETTING— server-level toggle, admin only
To write a protection listener, extend FlagListener:
public class MyListener extends FlagListener {
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onSomeEvent(SomeEvent e) {
checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.BLOCK_BREAK);
}
}checkIsland() handles rank comparison, event cancellation, and player notification automatically. All protection flag listeners live in listeners/flags/protection/.
// Island permission check
island.isAllowed(user, flag);
// Localized player messaging (never use player.sendMessage() directly)
user.sendMessage("protection.protected");
// Island lookup
Optional<Island> island = plugin.getIslands().getIslandAt(location);
// All managers accessed via singleton
plugin.getIslands() // IslandsManager
plugin.getIWM() // IslandWorldManager
plugin.getFlagsManager() // FlagsManagerThe test suite uses JUnit 5 + Mockito + MockBukkit. Almost every test class extends CommonTestSetup, which pre-wires ~20 mocks:
plugin— mockedBentoBoxinstancemockPlayer,world,location,island— standard game objectsiwm(IslandWorldManager),im(IslandsManager),lm(LocalesManager),fm(FlagsManager),hooksManager
Use CommonTestSetup as the base for new tests. Call super.setUp() in @BeforeEach and super.tearDown() in @AfterEach if overriding. The checkSpigotMessage(String) helper asserts messages sent to the player.
Test resources and temporary database files are cleaned up automatically by the base class teardown.
BentoBox is a plugin platform — its public API is compiled against by many external addons. Binary-incompatible changes cause NoSuchMethodError at runtime for all addons until they recompile.
- Changing the return type of a public method (the JVM encodes return type in the method descriptor; two methods cannot share name+params with different return types)
- Removing or renaming public methods/classes
- Adding required parameters to existing public methods
Automated rules (e.g. S4738 "Replace Guava types with Java stdlib") are appropriate for internal code but not for public API methods whose return type is part of the binary contract. Suppress selectively with a comment:
@SuppressWarnings("java:S4738") // ImmutableSet is intentional public API; changing return type is binary-incompatible
public ImmutableSet<UUID> getMemberSet() { ... }Guava (ImmutableSet, ImmutableList, etc.) is reliably available at runtime via Paper's bundled JARs and is safe to use in the public API.
User.getTranslation() returns a legacy §-coded string for backwards compatibility, even when the locale entry is MiniMessage. UI code (PanelItem.setDescription, etc.) then re-parses that legacy string back into a Component via Util.parseMiniMessageOrLegacy. This MiniMessage → Component → legacy → Component round-trip is lossy by default because of an Adventure quirk:
Adventure's LegacyComponentSerializer never emits §r to turn off a decoration when a sibling component clears it. Legacy color codes have no "decoration off" code — only §r resets — but Adventure simply omits the decoration code on the next sibling instead of resetting. When that legacy string is re-parsed under correct legacy semantics (decorations persist until §r), the decoration leaks into the following segment. This bit bold, italic, underlined, strikethrough, and obfuscated equally (#2917).
Util.componentToLegacy is therefore not a thin wrapper around Adventure's serializer — it's a custom Component walker (appendComponentLegacy / emitStyleTransition) that tracks the last-emitted color and decorations and inserts §r whenever any decoration was on and is now off, then re-applies color afterwards. Do not replace it with LegacyComponentSerializer.serialize() directly without re-introducing the leak. The round-trip is exercised by LegacyToMiniMessageTest.
User.convertToLegacy parses the whole translated string at once — never per-line. MiniMessage tags can span newlines (e.g. a <green>...\n...</green> block from a multi-line YAML entry, or a multi-line value substituted into a <green>[description]</green> template). Splitting on \n before parsing orphans close tags: the line bar</green> has no opening, and MiniMessage renders </green> as literal text in the lore. Adventure preserves newlines through text.content(), so a single parse handles everything correctly.
A template like <green>[description]</green> looks harmless but is a trap. Translation placeholders are substituted as legacy §-coded strings before re-parsing, and they may contain their own colors and newlines. Wrapping them re-introduces the multi-line orphaning problem above and forces the wrapper color over content that already has its own. Leave placeholders bare ([description]) and let the value bring its own colors. The protection.panel.flag-item.{description,menu,setting}-layout keys all follow this rule across every bundled locale.
componentToLegacy does not re-emit a color code when an adjacent text segment has the same color — it relies on the §-code carrying over within the contiguous string. Code that takes a translated legacy string and then .split("\\|") (or any literal-character split) breaks this carry-over: subsequent segments lose their color prefix and render in default. If a panel uses |-as-line-separator on a translated value, it must propagate the active §color/§format codes across the split itself, or set lore via Adventure Components instead of legacy Strings. (See addon-level/.../DonationPanel.java#splitWithStyleCarryover for a working pattern.) Bukkit's deprecated meta.setLore(List<String>) also does not suppress Minecraft's default lore italic — meta.lore(List<Component>) with the removeDefaultItalic helper does.
- The Gradle build uses the Paper
userdevplugin and Shadow plugin to produce a fat/shaded JAR atbuild/libs/BentoBox-{version}.jar. plugin.ymlandconfig.ymlare filtered for the${version}placeholder at build time; locale files are copied without filtering.- Java preview features are enabled for both compilation and test execution.
- Local builds produce version
3.13.0-LOCAL-SNAPSHOT; CI builds append-b{BUILD_NUMBER}-SNAPSHOT;origin/masterbuilds produce the bare version.
When you need to inspect source code for a dependency (e.g., BentoBox, addons):
- Check local Maven repo first:
~/.m2/repository/— sources jars are named*-sources.jar - Check the workspace: Look for sibling directories or Git submodules that may contain the dependency as a local project (e.g.,
../bentoBox,../addon-*) - Check Maven local cache for already-extracted sources before downloading anything
- Only download a jar or fetch from the internet if the above steps yield nothing useful
Prefer reading .java source files directly from a local Git clone over decompiling or extracting a jar.
In general, the latest version of BentoBox should be targeted.
Related projects are checked out as siblings under ~/git/:
Core:
bentobox/— core BentoBox framework
Game modes:
addon-acidisland/— AcidIsland game modeaddon-bskyblock/— BSkyBlock game modeBoxed/— Boxed game mode (expandable box area)CaveBlock/— CaveBlock game modeOneBlock/— AOneBlock game modeSkyGrid/— SkyGrid game modeRaftMode/— Raft survival game modeStrangerRealms/— StrangerRealms game modeBrix/— plot game modeparkour/— Parkour game modeposeidon/— Poseidon game modegg/— gg game mode
Addons:
addon-level/— island level calculationaddon-challenges/— challenges systemaddon-welcomewarpsigns/— warp signsaddon-limits/— block/entity limitsaddon-invSwitcher//invSwitcher/— inventory switcheraddon-biomes//Biomes/— biomes managementBank/— island bankBorder/— world border for islandsChat/— island chatCheckMeOut/— island submission/votingControlPanel/— game mode control panelConverter/— ASkyBlock to BSkyBlock converterDimensionalTrees/— dimension-specific treesdiscordwebhook/— Discord integrationDownloads/— BentoBox downloads siteDragonFights/— per-island ender dragon fightsExtraMobs/— additional mob spawning rulesFarmersDance/— twerking crop growthGravityFlux/— gravity addonGreenhouses-addon/— greenhouse biomesIslandFly/— island flight permissionIslandRankup/— island rankup systemLikes/— island likes/dislikesLimits/— block/entity limitslost-sheep/— lost sheep adventureMagicCobblestoneGenerator/— custom cobblestone generatorPortalStart/— portal-based island startpp/— pp addonRegionerator/— region managementResidence/— residence addonTopBlock/— top ten for OneBlockTwerkingForTrees/— twerking tree growthUpgrades/— island upgrades (Vault)Visit/— island visitingweblink/— web link addonCrowdBound/— CrowdBound addon
Data packs:
BoxedDataPack/— advancement datapack for Boxed
Documentation & tools:
docs/— main documentation sitedocs-chinese/— Chinese documentationdocs-french/— French documentationBentoBoxWorld.github.io/— GitHub Pages sitewebsite/— websitetranslation-tool/— translation tool
Check these for source before any network fetch.
world.bentobox:bentobox→~/git/bentobox/src/