A VERY lightweight Java library for creating PlaceholderAPI expansions using annotations. This library simplifies the process of creating placeholders by using method and field annotations instead of manual string parsing.
The library's main goal is to replace verbose, hard-to-maintain onPlaceholderRequest methods with clean, dedicated, and self-documenting annotated methods.
Without this library, you typically end up with a single large method full of if/else statements and manual string splitting to handle different placeholders.
@Override
public String onPlaceholderRequest(Player player, String params) {
if (params.equalsIgnoreCase("player_name")) {
return player.getName();
}
if (params.equalsIgnoreCase("player_balance")) {
// Balance logic...
return "100.42";
}
if (params.startsWith("player_stats_")) {
String[] parts = params.split("_");
if (parts.length == 3) {
String statType = parts[2];
// Logic to get stats...
}
}
return null;
}With this library, each placeholder is a separate, clearly defined method. The code is organized, readable, and easier to debug.
// Handles %myexpansion_player_name%
@Placeholder({"player", "name"})
public String getPlayerName(PlaceholderActor actor) {
return actor.getPlayer().getName();
}
// Handles %myexpansion_player_balance%
@Placeholder({"player", "balance"})
public double getBalance(PlaceholderActor actor) {
// Your balance logic here
return 100.42;
}
// Handles %myexpansion_player_stats_<stat-type>_<position>%
@Placeholder({"player", "stats"})
public int getStats(PlaceholderActor actor, String statType, int position) {
// Logic to get stats...
}- π·οΈ Annotation-based placeholders: Define placeholders using
@Placeholderon methods and fields. - β‘ SUPER Ligthweight: Only 22Ko added to your project
- π§ Type-safe parameter resolution: Automatic conversion of string parameters from the placeholder to Java types like
int,boolean,enum, etc. - π― Optional parameters: Support for optional placeholder parts with default values using
@Optional. - π¦ Varargs support: Handle a variable number of arguments in your placeholder methods.
- β‘ Caching system: Built-in caching with Time-To-Live (TTL) support via the
@Cacheannotation. - π Online player requirements: Restrict placeholders to online players using
@RequireOnlinePlayer. - π¨ Flexible configuration: Customize separators, logging, and register custom value resolvers.
Add the JitPack repository and the library dependency to your build configuration.
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>fr.robotv2</groupId>
<artifactId>PlaceholderAnnotationLib</artifactId>
<version>VERSION</version>
</dependency>
</dependencies>repositories {
// Other repositories...
maven { url = 'https://jitpack.io' }
}
dependencies {
// Other dependencies...
implementation 'fr.robotv2:PlaceholderAnnotationLib:VERSION' // Replace with the latest version
}Extend BasePlaceholderExpansion and use annotations to define your placeholders.
public class MyExpansion extends BasePlaceholderExpansion {
public MyExpansion(PlaceholderAnnotationProcessor processor) {
super(processor);
}
// This field handles %myexpansion_max_balance%
@Placeholder({"max", "balance"})
private final int maxBalance = 1000;
// This method handles %myexpansion_player_name%
@Placeholder({"player", "name"})
public String getPlayerName(PlaceholderActor actor) {
return actor.getPlayer().getName();
}
// This method handles %myexpansion_player_balance% and requires the player to be online
@Placeholder({"player", "balance"})
@RequireOnlinePlayer
public double getBalance(PlaceholderActor actor) {
// Your balance logic here
return 100.42;
}
// --- Standard PlaceholderAPI Methods ---
@Override
public String getIdentifier() {
return "myexpansion";
}
@Override
public String getAuthor() {
return "YourName";
}
@Override
public String getVersion() {
return "1.0.0";
}
}In your main plugin class, instantiate the PlaceholderAnnotationProcessor and your expansion, then register it.
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
// Create the processor with desired configuration
PlaceholderAnnotationProcessor processor = new PlaceholderAnnotationProcessor.Builder()
.separator('_')
.debug(false)
.build();
// Register your expansion
MyExpansion expansion = new MyExpansion(processor);
expansion.register(); // Registers the expansion with PlaceholderAPI
}
}Your placeholders are now available through PlaceholderAPI:
%myexpansion_max_balance%β Returns the value of themaxBalancefield.%myexpansion_player_name%β Returns the player's name.%myexpansion_player_balance%β Returns the player's balance, only if they are online.
Defines a placeholder on a method or field. The string array defines the parts of the placeholder, which will be joined by the configured separator.
// Handles: %myexpansion_player_stats_kills%
@Placeholder({"player", "stats", "kills"})
public String getKills(PlaceholderActor actor) { /* ... */ }
// Handles: %myexpansion_player_stats_deaths%
@Placeholder({"player", "stats", "deaths"})
public String getDeaths(PlaceholderActor actor) { /* ... */ }Restricts a placeholder to online players. If the player is offline, the placeholder will not be processed and will appear in its raw form.
@Placeholder({"player", "location"})
@RequireOnlinePlayer
public String getLocation(PlaceholderActor actor) {
// This call is safe; it will not throw a NullPointerException.
Player player = actor.requireOnlinePlayer();
Location location = player.getLocation();
return "X: " + location.getX() + ", Y: " + location.getY() + ", Z: " + location.getZ();
}Marks a method parameter as optional and provides a default value if it is not supplied in the placeholder string.
@Placeholder({"player", "stats"})
public String getStats(PlaceholderActor actor,
String statType,
@Optional(defaultParameter = "kills") String anotherParameter) {
// For %myexpansion_player_stats_deaths%: statType = "deaths", anotherParameter = "kills"
// For %myexpansion_player_stats_deaths_assists%: statType = "deaths", anotherParameter = "assists"
return statType + ": " + anotherParameter;
}Enables caching for the result of a method. This is useful for expensive operations that do not need to be re-calculated on every request.
// The result of this method will be cached for 30 seconds.
@Placeholder({"server", "tps"})
@Cache(value = 30, unit = TimeUnit.SECONDS)
public double getTPS() {
// Expensive TPS calculation that now runs at most once every 30 seconds.
return calculateTPS();
}Defines a fallback method to be executed when a requested placeholder does not match any other defined placeholder.
@DefaultPlaceholder
public String handleUnknown() {
return "Unknown placeholder.";
}The library automatically converts string segments from the placeholder into corresponding Java types for method parameters.
Supported types include String, primitives (int, double, etc.), primitive wrappers (Integer, Double, etc.), boolean, enum types, and String... (varargs).
Example:
For a placeholder like %myexpansion_player_give_item_DIAMOND_64_true_Shiny_Precious%
@Placeholder({"player", "give", "item"})
public String giveItem(PlaceholderActor actor,
Material material, // "DIAMOND" is resolved to Material.DIAMOND
int amount, // "64" is parsed to an int
boolean silent, // "true" is parsed to a boolean
String... lore) { // "Shiny", "Precious" become a String array
// Implementation...
return "Gave " + amount + " " + material.name();
}You can use @Placeholder on fields for values that are constant or do not require logic to compute.
public class MyExpansion extends BasePlaceholderExpansion {
@Placeholder({"server", "version"})
private final String serverVersion = "1.20.1";
@Placeholder({"plugin", "name"})
private String pluginName = "MyAwesomePlugin";
// ...
}You can register your own converters for custom types.
// In your onEnable method, configure the processor:
processor.registerValueResolver(UUID.class, (actor, value) -> {
try {
return UUID.fromString(value);
} catch (IllegalArgumentException e) {
// Fallback to the player's UUID if the parameter is invalid or missing.
return actor.getPlayer().getUniqueId();
}
});- Java 8+
- Bukkit/Spigot/Paper
- PlaceholderAPI
- Open an issue on GitHub for bugs or feature requests.
- Review the examples in the
testdirectory of the project repository. - Enable debug mode (
.debug(true)) in the processor builder for detailed logging.