Announcement Paper & Velocity 1.20.4

The 1.20.4 Update​

Stable Paper and Velocity 1.20.4 builds have been released! As always, backups are absolutely mandatory. After upgrading your world to 1.20.4, you cannot downgrade back to a lower version!

We would like to thank everyone that worked on this update (a lot of people and work needed for a minor update, once again):
If you'd like to support PaperMC as a whole, you can find more information at

Discord Update Announcements​

From now on, instead of creating a new Discord channel for every update, we will post important milestone updates (such as the availability of experimental builds) into the new update-announcements channel and provide more small-stepped info in the forum channel below it. You might have to add these channels to your list via "Channels & Roles" at the top of the channel list first.

For Developers​

New API​

With the new sendResourcePacks and removeResourcePacks methods, you can give each pack its own UUID to be individually added and removed later, which means that you can have multiple packs applied at once! The existing setResourcePack method will override all previous ones to retain expected behavior.

Keyed interface may be removed on some types​

Keyed provides a NamespacedKey getKey() to get keys for biomes, item and block types, sounds, etc. However, trim patterns and trim materials mark the first two registry based objects that do not require a key in all cases, hence the nonnull getKey method is not valid for these.

To make your plugins future proof of such cases, please use the newly added Registry#getKey(Object). While the getKey methods will be available until actually broken, using the method on Registry will make sure your plugin does not suddenly break later. Note that because of the possibility of no key existing, this method is nullable. If you are sure one will exist, you can also use the nonnull Registry#getKeyOrThrow.

Hangar login/signup via GitHub, Google, or Microsoft account​

As per the last big announcement, we now have our own website for you to upload your Paper, Bungee, and Velocity plugins to:
If you don't feel like manually uploading your builds to it, you can also check out our hangar publish gradle plugin:

Additionally, we have prepared a little Christmas gift for all (current or future) Hangar users: You can now use your GitHub, Google or Microsoft account to login to Hangar. If you don't have an account yet, you can signup using one of these OAuth providers on the signup page, if you want to link an OAuth account to your existing account you can do so in the security settings. Note that this functionality, while thoroughly tested, is still a bit experimental and the UX of the flows and the design of the UI is still subject to change. Please send us your feedback on Discord or via the issue tracker.

Here are a few reminders of other important future changes​

CraftBukkit package relocation

This is very important if you for whatever reason use reflection to either
  • parse the relocated package version.
  • call CB internals.
At some point in the future, we will only provide jars without relocation, given it is a nonsensical practice resulting in unavoidably bad code design and unexpected incompatibilities in a large number of plugins. While we will be able to automagically remap both direct and reflective calls to the relocated package, parsing the package version is not supported and WILL break at some point in the future. The changes we have planned should make working with internals a lot easier, since we recognize that sometimes (though not as often as some might think) there is no better alternative.

If you reflect on CB classes
Easy, just don't try to parse the package version. The following will work on servers with and without CB relocation:
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();

public static String cbClass(String clazz) {
    return CRAFTBUKKIT_PACKAGE + "." + clazz);


If you try to parse the server version
Do NOT do this:
String craftBukkitPackage = Bukkit.getServer().getClass().getPackage().getName();
// This is the *bad* part, including any other parsing of the version
String version = craftBukkitPackage.substring(craftBukkitPackage.lastIndexOf('.') + 1);
if (version.equals("v1_20_R1")) {
    // ...
} else {
  // Unsupported
Instead, use long-standing API:
// Paper method that was added in 2020
// Example value: 1.20.1
String minecraftVersion = Bukkit.getServer().getMinecraftVersion();

// Bukkit method that was added in 2011
// Example value: 1.20.1-R0.1-SNAPSHOT
String bukkitVersion = Bukkit.getServer().getBukkitVersion();

if (minecraftVersion.equals("1.20.1")) {
    // ...
} else {
  // Assume latest still works, or error as unsupported
  // Alternatively for extra compatibility, check if
  // the latest package version is valid by catching
  // ClassNotFoundException with: Class.forName("org.bukkit.craftbukkit.v1_20_R1.CraftServer")
The Minecraft version strings you can parse and evaluate to your heart's content. Another (less recommended) alternative is getting the server protocol version from Bukkit.getUnsafe.

Future changes regarding API enums

Enums such as Biome will be converted to classes with public static final objects at some point. While some backwards compatibility will be provided, please try to avoid the use of switch statements, EnumMap and EnumSet on these, including the Material enum.