B
BlackSpigot Guides
Guest
Goal and scope
Build a separate plugin that sends a private greeting about one second after a player joins. The delay uses server ticks, so it takes longer when the server is lagging. This is a conventional Spigot scheduler example for Java 21 / Spigot API 1.21.1; it is not a Folia scheduling example.
1. Make a clean second project
Use the Maven project layout from the /workshop command tutorial, set artifactId to
This listener must be registered during enable. Merely writing an annotated method does not subscribe it to events. The Spigot event guide explains listener registration and handler signatures.
2. Add the bundled files
Save this as
Save this as
Build with
3. Understand the lifecycle
The event happens on join. The callback runs once after 20 ticks through the synchronous scheduler. It resolves the player again by UUID and checks they are still connected. A player who leaves before the callback is simply skipped. Keeping a UUID instead of a long-lived Player reference also makes the delayed action easier to reason about.
Do not add Thread.sleep to an event handler: that would hold up the server thread. Do not replace this with an asynchronous task that accesses players or the world. The scheduler API documents the synchronous task and async access constraints. For file or network work, separate background I/O from the server-thread action and handle failures explicitly; that is outside this minimal exercise.
4. Run the checks
Join normally: expect exactly one private message. Leave immediately after connecting: the server should produce no callback exception. Rejoin normally: expect a new message. Change the configuration while stopped and restart: expect the replacement text. Restart with the plugin removed: no greeting should remain. If messages appear twice, first look for duplicate JARs, duplicate registration or a second plugin sending the same text.
An intentional boundary
This sends one message per join, not one per account lifetime. If a player leaves and reconnects within the delay, an earlier callback can see that player online again. A once-per-session or once-ever requirement needs explicit session/persistence logic; do not claim this example provides it. For a simple greeting, keep the behavior small and visible before adding state.
Validation: Java compiled against the stated API. The join/leave and console checks above still require your development server.
Build a separate plugin that sends a private greeting about one second after a player joins. The delay uses server ticks, so it takes longer when the server is lagging. This is a conventional Spigot scheduler example for Java 21 / Spigot API 1.21.1; it is not a Folia scheduling example.
1. Make a clean second project
Use the Maven project layout from the /workshop command tutorial, set artifactId to
workshop-welcome, and keep its provided Spigot API dependency and compiler settings. Replace the Java file with src/main/java/com/example/workshop/WelcomePlugin.java; do not leave the old main class in your plugin descriptor. The complete event handler is:
Java:
package com.example.workshop;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
public final class WelcomePlugin extends JavaPlugin implements Listener {
@Override public void onEnable() {
saveDefaultConfig();
getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler public void onJoin(PlayerJoinEvent event) {
UUID id = event.getPlayer().getUniqueId();
getServer().getScheduler().runTaskLater(this, () -> {
Player player = getServer().getPlayer(id);
if (player == null || !player.isOnline()) return;
player.sendMessage(getConfig().getString("message", "Welcome back!"));
}, 20L);
}
}
2. Add the bundled files
Save this as
src/main/resources/plugin.yml:
YAML:
name: WorkshopWelcome
version: 1.0.0
main: com.example.workshop.WelcomePlugin
api-version: '1.21'
src/main/resources/config.yml:
YAML:
message: 'Welcome! Use the workshop forums if you get stuck.'
mvn clean package. Stop your development server, install the resulting workshop-welcome JAR, then restart. Watch startup output before joining.3. Understand the lifecycle
The event happens on join. The callback runs once after 20 ticks through the synchronous scheduler. It resolves the player again by UUID and checks they are still connected. A player who leaves before the callback is simply skipped. Keeping a UUID instead of a long-lived Player reference also makes the delayed action easier to reason about.
Do not add Thread.sleep to an event handler: that would hold up the server thread. Do not replace this with an asynchronous task that accesses players or the world. The scheduler API documents the synchronous task and async access constraints. For file or network work, separate background I/O from the server-thread action and handle failures explicitly; that is outside this minimal exercise.
4. Run the checks
Join normally: expect exactly one private message. Leave immediately after connecting: the server should produce no callback exception. Rejoin normally: expect a new message. Change the configuration while stopped and restart: expect the replacement text. Restart with the plugin removed: no greeting should remain. If messages appear twice, first look for duplicate JARs, duplicate registration or a second plugin sending the same text.
An intentional boundary
This sends one message per join, not one per account lifetime. If a player leaves and reconnects within the delay, an earlier callback can see that player online again. A once-per-session or once-ever requirement needs explicit session/persistence logic; do not claim this example provides it. For a simple greeting, keep the behavior small and visible before adding state.
Validation: Java compiled against the stated API. The join/leave and console checks above still require your development server.
