A lightweight, Folia-safe preferences system for Paper/Folia servers that lets players manage personal settings via a single command and lets other plugins register new preferences (boolean / integer / number) through a simple API.
This plugin persists preferences in MySQL using HikariCP and provides:
/preferencescommand with tab completionlist,reset,resetall/preferences <option>details view (title + description)- Public API for other plugins to register options
- In-memory caching for fast reads
PreferenceChangedEventfor reactive integrations- Folia-safe scheduling (no Bukkit async scheduler usage)
- Main class:
com.earthpol.preferences.Preferences
/preferences— show paged list (same as/preferences list 1)/preferences list [page]— show options and current values/preferences <option>— show details (title, description, current/default)/preferences <option> <value>— set a preference/preferences reset <option>— reset an option to default/preferences resetall— reset all preferences to defaults
Typing:
/preferences siegewar.killfeed
Shows:
----[ SiegeWar Killfeed ]----
Preference: (default: true) (TRUE/FALSE (on/off, 1/0))
Description: Display SiegeWar kills in the chat.
Set: /preferences siegewar.killfeed <value>
Reset: /preferences reset siegewar.killfeed
-
BooleanOption
Accepts:true/false,on/off,yes/no,1/0 -
IntOption
Bounded integers (e.g. 0–100) -
NumberOption
Bounded doubles (e.g. 0.5–2.0)
- Paper or Folia
- Java 17+
- MySQL / MariaDB
- Maven (recommended for building)
-
Build the plugin:
mvn clean package
-
Copy the jar from:
target/EarthPolPreferences-*.jar
-
Into your server:
plugins/
-
Start the server once to generate the config.
-
Configure MySQL in:
plugins/EarthPolPreferences/config.yml
-
Restart the server.
mysql:
host: "127.0.0.1"
port: 3306
database: "earthpol"
username: "root"
password: ""
maximumPoolSize: 10
minimumIdle: 2
connectionTimeoutMs: 10000
leakDetectionThresholdMs: 0Notes:
- MariaDB works with the MySQL connector in most setups.
- The table schema is auto-created on startup.
The plugin auto-creates:
CREATE TABLE IF NOT EXISTS ep_preferences (
uuid CHAR(36) NOT NULL,
`key` VARCHAR(64) NOT NULL,
`value` VARCHAR(64) NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (uuid, `key`),
INDEX (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;uuid= player UUIDkey= option key (siegewar.killfeed)value= stored as string (true,12,1.5, etc.)
Use namespaced keys to avoid collisions:
siegewar.killfeedsentra.notifyearthpol.map.opacity
If namespace enforcement is enabled in the registry, keys must contain a dot (.).
/preferences
/preferences list
/preferences list 2
/preferences siegewar.killfeed
/preferences siegewar.killfeed false
/preferences siegewar.killfeed off
/preferences chat.scale 1.25
/preferences hud.opacity 80
/preferences reset siegewar.killfeed
/preferences resetall
There are two common approaches:
- Soft-depend at runtime (for public plugins)
- Compile-time dependency via Maven (for controlled ecosystems)
You can use both.
From the EarthPolPreferences project folder:
mvn clean installThen, in your other plugin’s pom.xml:
<dependency>
<groupId>com.earthpol</groupId>
<artifactId>earthpol-preferences</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>Use provided because the server supplies the plugin at runtime.
In your other plugin’s plugin.yml:
softdepend: [EarthPolPreferences]Or, if required:
depend: [EarthPolPreferences]PreferencesApi api = getServer().getServicesManager().load(PreferencesApi.class);
if (api == null) {
getLogger().warning("EarthPolPreferences not found; skipping preference integration.");
return;
}public static final BooleanOption KILLFEED =
new BooleanOption(
"myplugin.killfeed",
"Killfeed",
"Display kill messages in chat.",
true
);
api.registry().register(KILLFEED);boolean enabled = api.service().get(player.getUniqueId(), MyPlugin.KILLFEED);If the player never set it, the default value is returned.
EarthPolPreferences fires PreferenceChangedEvent on the player scheduler (Folia-safe).
@EventHandler
public void onPrefChange(PreferenceChangedEvent e) {
if (!e.getKey().equalsIgnoreCase(MyPlugin.KILLFEED.key())) return;
// e.getNewValue() is a string ("true", "false", etc.)
// null means reset to default
}Register normally:
getServer().getPluginManager().registerEvents(new PrefListener(), this);- All DB work happens off-thread in a dedicated executor
- Events fire on the player’s scheduler
- It is safe to touch player/world state in
PreferenceChangedEvent - Do not access Bukkit API from your own async threads
This README intentionally uses ````` for all code sections so you can paste it into another markdown block and replace them later.