Getting Started with Paper

Why Choose Paper?

Paper offers several advantages over standard Bukkit/Spigot servers:

  • Improved performance - Paper includes numerous optimizations to improve server performance
  • Enhanced API - Paper extends the Bukkit/Spigot API with additional features
  • Better stability - Many bug fixes and improvements for a more stable server
  • Active development - Regular updates and a responsive development team
  • Bukkit/Spigot compatibility - Most Bukkit/Spigot plugins work on Paper without modification

Setting Up Your Development Environment

Setting up for Paper development is similar to Bukkit/Spigot, with a few differences:

  1. Install Java Development Kit (JDK) 17 or higher

    Paper requires JDK 17 or higher for the latest versions. Download it from Adoptium or Oracle.

  2. Choose an IDE

    We recommend IntelliJ IDEA or Eclipse for Java development.

  3. Set up a build system

    Maven or Gradle are recommended for managing dependencies and building your plugin.

  4. Add the Paper API

    Instead of using the Spigot API, use the Paper API as your dependency:

    pom.xml (Paper API Dependency)
    123456789101112
    <!-- Maven -->
    <repository>
        <id>papermc</id>
        <url>https://repo.papermc.io/repository/maven-public/</url>
    </repository>
    
    <dependency>
        <groupId>io.papermc.paper</groupId>
        <artifactId>paper-api</artifactId>
        <version>1.20.4-R0.1-SNAPSHOT</version>
        <scope>provided</scope>
    </dependency>

    Or for Gradle:

    build.gradle (Paper API Dependency)
    12345678910
    // Gradle
    repositories {
        maven {
            url = uri("https://repo.papermc.io/repository/maven-public/")
        }
    }
    
    dependencies {
        compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
    }

Paper-Specific Features

Enhanced Event API

Paper adds new events and enhances existing ones. Here's an example of using a Paper-specific event:

ChatListener.java
123456789101112131415161718
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public class ChatListener implements Listener {
    @EventHandler
    public void onPlayerChat(AsyncChatEvent event) {
        // Paper uses Adventure API for text components
        Component message = event.message();

        // Modify the message
        Component newMessage = Component.text("[Modified] ").append(message);
        event.message(newMessage);

        // This event is async, so be careful with what you do here
    }
}

Adventure API

Paper uses the Adventure API for text components, which provides a more powerful way to create rich text:

AdventureExample.java
12345678910111213141516
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import org.bukkit.entity.Player;

public void sendFancyMessage(Player player) {
    Component message = Component.text("Click me!")
        .color(NamedTextColor.GOLD)
        .decorate(TextDecoration.BOLD)
        .hoverEvent(HoverEvent.showText(Component.text("Click to open website")))
        .clickEvent(ClickEvent.openUrl("https://papermc.io"));

    player.sendMessage(message);
}

Asynchronous API

Paper provides more async APIs to help improve server performance:

AsyncExample.java
12345678910111213141516171819202122
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

public class AsyncExample extends JavaPlugin {
    public void teleportPlayerAsync(Player player, Location location) {
        // Load the chunk asynchronously before teleporting
        World world = location.getWorld();
        int chunkX = location.getBlockX() >> 4;
        int chunkZ = location.getBlockZ() >> 4;

        world.getChunkAtAsync(chunkX, chunkZ).thenAccept(chunk -> {
            // Now that the chunk is loaded, teleport the player (on the main thread)
            getServer().getScheduler().runTask(this, () -> {
                player.teleport(location);
                player.sendMessage("Teleported to pre-loaded chunk!");
            });
        });
    }
}

Best Practices for Paper Plugins

Compatibility Considerations

When developing for Paper, consider these best practices:

  • Check for Paper - If using Paper-specific features, check if the server is running Paper
  • Fallback implementations - Provide fallbacks for Bukkit/Spigot servers when possible
  • Use Adventure API - Leverage the Adventure API for text components
  • Async where possible - Use Paper's async APIs to improve performance

Example of checking if the server is running Paper:

PaperCheck.java
12345678
public boolean isPaperServer() {
    try {
        Class.forName("io.papermc.paper.event.player.AsyncChatEvent");
        return true;
    } catch (ClassNotFoundException e) {
        return false;
    }
}

Paper Configuration

Paper adds many server configuration options that can affect your plugin:

  • paper-global.yml - Global settings that affect all worlds
  • paper-world-defaults.yml - Default settings for all worlds
  • paper-world/<world>.yml - Settings for specific worlds

Be aware of these configurations as they might affect your plugin's behavior. For example, some events might be disabled or modified by Paper's configuration.

Paper API Extensions

Paper extends many Bukkit/Spigot classes with additional methods. Here are some examples:

PaperExtensions.java
1234567891011121314151617181920
// Paper-specific methods on Player
Player player = event.getPlayer();

// Get the player's client brand (what client they're using)
String clientBrand = player.getClientBrandName();

// Check if the player is in a vehicle
boolean inVehicle = player.isInsideVehicle();

// Get the player's locale
String locale = player.locale().toString();

// Paper-specific methods on World
World world = player.getWorld();

// Get the world's spawn location with yaw and pitch
Location spawnLocation = world.getSpawnLocation();

// Check if a chunk is generated
boolean isChunkGenerated = world.isChunkGenerated(chunkX, chunkZ);