to jump!", with some coloring and styling.
final TextComponent textComponent2 = Component.text()
.content("You're a ")
.color(TextColor.color(0x443344))
.append(Component.text().content("Bunny").color(NamedTextColor.LIGHT_PURPLE))
.append(Component.text("! Press "))
.append(
Component.keybind().keybind("key.jump")
.color(NamedTextColor.LIGHT_PURPLE)
.decoration(TextDecoration.BOLD, true)
.build()
)
.append(Component.text(" to jump!"))
.build();
// now you can send `textComponent2` to something, such as a client
```
## Styling components
Styles are a superset of TextColor and TextDecoration and can be applied to text components.
TextColor represents any color in the RGB spectrum.
You can also use NamedTextColor to choose from the default color palette.
The following TextDecorations are available:
* *Italic*
* **Bold**
* Strikethrough
* Underlined
* Obfuscated
## Events
There are currently two types of events available for text components.
Hover events allow you to show another component, item or entity when a user hovers their mouse over the text.
When a user clicks on the text component, a click event is fired which can perform one of the following actions:
* Open a URL
* Open a file
* Run a command
* Suggest a command
* Change a book's page
* Copy a string to clipboard
* Open a dialog
* Send a custom payload back to the server
## Serializing and deserializing components
Serialization to JSON, legacy, and plain representations is also
supported.
Components can be serialized with [Text Serializers](/adventure/serializer).
## Using components within your application
The way you use components within your application will of course vary
depending on what you're aiming to achieve.
However, the most common task is likely to be sending a component to
some sort of Minecraft client. The method for doing this will depend on
the platform your program is running on, however it is likely to involve
serializing the component to Minecraft's JSON format, and then sending
the JSON through another method provided by the platform.
The text library is platform-agnostic and therefore doesn't provide any
way to send components to clients. Some platforms implement [Adventure natively](/adventure/platform/native), so `Components`
can be directly used with their API. For other platforms (Spigot/Bukkit, BungeeCord, and SpongeAPI 7),
we provide compatibility bridges as [Platforms](/adventure/platform) which can be distributed with your own plugins.
---
# Titles
Displaying titles to players.
## Constructing a Title
Titles are composed of:
* A component used for the main title
* A component used for the subtitle
* Optionally, a `Title.Times` object can be used to determine the fade-in, stay on screen and fade-out durations
**Examples:**
```java
public void showMyTitle(final Audience target) {
final Component mainTitle = Component.text("This is the main title", NamedTextColor.WHITE);
final Component subtitle = Component.text("This is the subtitle", NamedTextColor.GRAY);
// Creates a simple title with the default values for fade-in, stay on screen and fade-out durations
final Title title = Title.title(mainTitle, subtitle);
// Send the title to your audience
target.showTitle(title);
}
public void showMyTitleWithDurations(final Audience target) {
final Title.Times times = Title.Times.times(Duration.ofMillis(500), Duration.ofMillis(3000), Duration.ofMillis(1000));
// Using the times object this title will use 500ms to fade in, stay on screen for 3000ms and then fade out for 1000ms
final Title title = Title.title(Component.text("Hello!"), Component.empty(), times);
// Send the title, you can also use Audience#clearTitle() to remove the title at any time
target.showTitle(title);
}
```
---
# Version history
Pages dedicated to displaying the latest changelogs!
import { CardGrid, LinkCard } from "@astrojs/starlight/components";
These changelogs for individual projects mirror those posted on the GitHub Releases page of each project's repository.
---
# Folia Documentation
import { CardGrid, LinkCard } from "@astrojs/starlight/components";
Folia is a fork of Paper which adds regionized multithreading to the dedicated server.
---
# Administration
import PageCards from "/src/components/PageCards.astro";
Welcome to the Folia administration guide! This guide includes information and tutorials
regarding the administration of a Folia server.
#### Reference
---
# Reference
import PageCards from "/src/components/PageCards.astro";
A reference of how Folia works.
---
# Frequently asked questions
Questions frequently asked by our community, answered by us!
## What server types can benefit from Folia?
Server types that naturally spread players out,
like skyblock or SMP, will benefit the most from Folia. The server
should have a sizeable player count, too.
## What hardware will Folia run best on?
Ideally, at least 16 _cores_ (not threads).
## How to best configure Folia?
First, it is recommended that the world is pre-generated so that the number
of chunk system worker threads required is reduced greatly.
The following is a _very rough_ estimation based off of the testing
done before Folia was released on the test server we ran that
had ~330 players peak. So, it is not exact and will require further tuning -
just take it as a starting point.
The total number of cores on the machine available should be
taken into account. Then, allocate threads for:
- netty IO: ~4 per 200-300 players
- chunk system io threads: ~3 per 200-300 players
- chunk system workers if pre-generated, ~2 per 200-300 players
- There is no best guess for chunk system workers if not pre-generated, as
on the test server we ran we gave 16 threads but chunk generation was still
slow at ~300 players.
- GC Settings: ???? But, GC settings _do_ allocate concurrent threads, and you need
to know exactly how many. This is typically through the `-XX:ConcGCThreads=n` flag. Do not
confuse this flag with `-XX:ParallelGCThreads=n`, as parallel GC threads only run when
the application is paused by GC and as such should not be taken into account.
After all of that allocation, the remaining cores on the system until 80%
allocation (total threads allocated < 80% of cpus available) can be
allocated to tickthreads (under global config, `threaded-regions.threads`).
The reason you should not allocate more than 80% of the cores is due to the
fact that plugins or even the server may make use of additional threads
that you cannot configure or even predict.
Additionally, the above is all a rough guess based on player count, but
it is very likely that the thread allocation will not be ideal, and you
will need to tune it based on usage of the threads that you end up seeing.
## What commands does Folia disable?
Folia currently disables a handful of commands. These are them:
- Bossbar commands
- Clone commands
- Data commands
- Datapack
- Debug
- Function
- Item commands
- Loot
- Reload
- Return
- Ride
- Rotate
- Schedule
- Scoreboard
- Spectate
- SpreadPlayers
- Tag
- Team
- TeamMsg
- Tick
- Trigger
- Perf
- SaveAll
- Restart
---
# Overview
An overview of how Folia works.
Described in this document is the abstract overview
of changes done by Folia. Folia splits the chunks within all loaded worlds
into independently ticking regions so that the regions are ticked
independently and in parallel. Described first will be intra region
operations, and then inter region operations.
## Rules for independent regions
In order to ensure that regions are independent, the rules for
maintaining regions must ensure that a ticking region
has no directly adjacent neighbor regions which are ticking.
The following rules guarantee the invariant is upheld:
1. Any ticking region may not grow while it is ticking.
2. Any ticking region must initially own a small buffer of chunks outside
its perimeter.
3. Regions may not _begin_ to tick if they have a neighboring adjacent
region.
4. Adjacent regions must eventually merge to form a single region.
Additionally, to ensure that a region is not composed of independent regions
(which would hinder parallelism), regions composed of more than
one independent area must be eventually split into independent regions
when possible.
Finally, to ensure that ticking regions may store and maintain data
about the current region (i.e. tick count, entities within the region, chunks
within the region, block/fluid tick lists, and more), regions have
their own data object that may only be accessed while ticking the region and
by the thread ticking the region. Also, there are callbacks to merging
or splitting regions so that the data object may be updated appropriately.
The implementation of these rules is described in [Region Logic](/folia/reference/region-logic).
The end result of applying these rules is that a ticking region can ensure that
only the current thread has write access to any data contained within the region,
and that at any given time the number of independent regions is close to maximum.
## Intra-region operations
Intra-region operations refer to any operations that only deal with data
for a single region by the owning region, or to merge/split logic.
### Ticking for independent regions
Independent regions tick independently and in parallel. To tick independently
means that regions maintain their own deadlines for scheduling the next tick. For
example, consider two regions A and B such that A's next tick start is at t=15ms
and B's next tick start is at t=0ms. Consider the following sequence of events:
1. At t = 0ms, B begins to tick.
2. At t = 15ms, A begins to tick.
3. At t = 20ms, B is finished its tick. It is then scheduled to tick again at t = 50ms.
4. At t = 50ms, B begins its 2nd tick.
5. At t = 70ms, B finishes its 2nd tick and is scheduled to tick again at t = 100ms.
6. At t = 95ms, A finishes its _first_ tick. It is scheduled to tick again at t = 95ms.
It is important to note that at no time was B's schedule affected by the fact that
A fell behind its 20TPS target.
To implement the described behavior, each region maintains a repeating
task on a scheduled executor (see `SchedulerThreadPool`) that schedules
tasks according to an earliest-start-time-first scheduling algorithm. The
algorithm is similar to EDF, but schedules according to start time. However,
given that the deadline for each tick is 50ms + the start time, it behaves
identically to the EDF algorithm.
The EDF-like algorithm is selected so that as long as the thread pool is
not maximally utilized, that all regions that take <= 50ms to tick will
maintain 20TPS. However, the scheduling algorithm is neither NUMA aware
nor CPU core aware - it will not make attempts (when n regions > m threads)
to pin regions to certain cores.
Since regions tick independently, they maintain their own tick counters. The
implications of this are described in the next section.
### Tick counters
In standard Vanilla, there are several important tick counters: Current Tick,
Game Time Tick, and Daylight Time Tick. The Current Tick counter is used
for determining the tick number since the server has booted. The Game Time
Tick is maintained per world and is used to schedule block ticks
for redstone, fluids, and other physics events. The Daylight Time Tick
is simply the number of ticks since noon, maintained per world.
In Folia, the Current Tick is maintained per region. The Game Time Tick
is split into two counters: Redstone Time and Global Game Time.
Redstone Time is maintained per region. Global Game Time and
Daylight Time are maintained by the "global region."
At the start of each region tick, the global game time tick and
daylight time tick are copied from the global region and any time
the current region retrieves those values, it will retrieve from
the copy received at the start of tick. This is to ensure that
for any two calls to retrieve the tick number throughout the tick,
that those two calls report the same tick number.
The global game time is maintained for a couple of reasons:
1. There needs to be a counter representing how many ticks a world
has existed for, since the game does track total number of days
the world has gone on for.
2. Significant amounts of new entity AI code uses game time (for
a reason I cannot divine) to store absolute deadlines of tasks.
It is not impossible to write code to adjust the deadlines of
all of these tasks, but the amount of work is significant.
#### Global region
The global region is a single scheduled task that is always scheduled
to run at 20TPS that is responsible for maintaining data that is not
tied to any specific region: game rules, global game time, daylight time,
console command handling, world border, weather, and others. Unlike the other
regions, the global region does not need to perform any special logic
for merging or splitting because it is never split or merged - there is
only one global region at any time. The global region does not own
any region specific data.
#### Merging and splitting region tick times
Since redstone and current ticks are maintained per region, there needs
to be appropriate logic to adjust the tick deadlines used by the block/fluid
tick scheduler and anything else that schedules by redstone/current
absolute tick time so that the relative deadline is unaffected.
When merging a region x (from) into a region y (into or to),
we can either adjust both the deadlines of x and y or just one of x and y.
It is simply easier to adjust one, and arbitrarily the region x is chosen.
Then, the deadlines of x must be adjusted so that considering the current
ticks of y that the relative deadlines remain unchanged.
Consider a deadline d1 = from tick + relative deadline in region x.
We then want the adjusted deadline d2 to be d2 = to tick + relative deadline
in region y, so that the relative tick deadline is maintained. We can
achieve this by applying an offset o to d1 so that d1 + o = d2, and the
offset used is o = tick to - tick from. This offset must be calculated
for redstone tick and current tick separately, since the logic to increase
redstone tick can be turned off by the `Level#tickTime` field.
Finally, the split case is easy - when a split occurs,
the independent regions from the split inherit the redstone/current tick
from the parent region. Thus, the relative deadlines are maintained as there
is no tick number change.
In all cases, redstone or any other events scheduled by current tick
remain unaffected when regions split or merge as the relative deadline
is maintained by applying an offset in the merge case and by copying
the tick number in the split case.
## Inter-region operations
Inter-region operations refer to operations that work with other regions that are not
the current ticking region that are in a completely unknown state. These
regions may be transient, may be ticking, or may not even exist.
### Utilities to assist operations
In order to assist in inter region operations, several utilities are provided.
In NMS, these utilities are the `EntityScheduler`, the `RegionizedTaskQueue`,
the global region task queue, and the region-local data provider
`RegionizedData`. The Folia API has similar analogues, but does not have
a region-local data provider as the NMS data provider holds critical
locks and is invoked in critical areas of code when performing any
callback logic and is thus highly susceptible to fatal plugin errors
involving lengthy I/O or world state modification.
#### `EntityScheduler`
The `EntityScheduler` allows tasks to be scheduled to be executed on the
region that owns the entity. This is particularly useful when dealing
with entity teleportation, as once an entity begins an asynchronous
teleport the entity cannot tick until the teleport has completed, and
the timing is undefined.
#### `RegionizedTaskQueue`
The `RegionizedTaskQueue` allows tasks to be scheduled to be executed on
the next tick of a region that owns a specific location, or creating
such region if it does not exist. This is useful for tasks that may
need to edit or retrieve world/block/chunk data outside the current region.
#### Global region task queue
The global region task queue is simply used to perform edits on data
that the global region owns, such as game rules, day time, weather,
or to execute commands using the console command sender.
#### `RegionizedData`
The `RegionizedData` class allows regions to define region-local data,
which allow regions to store data without having to consider concurrent
data access from other regions. For example, current per region
entity/chunk/block/fluid tick lists are maintained so that regions do not
need to consider concurrent access to these data sets.
The utilities allow various cross-region issues to be resolved in a
simple fashion, such as editing block/entity/world state from any region
by using tasks queues, or by avoiding concurrency issues by using
RegionizedData. More advanced operations such as teleportation,
player respawning, and portalling, all make use of these utilities
to ensure the operation is thread-safe.
### Entity intra- and inter-dimension teleports
Entities need special logic in order to teleport safely between
other regions or other dimensions. In all cases however, the call to
teleport/place an entity must be invoked on the region owning the entity.
The `EntityScheduler` can be used to easily schedule code to execute in such
a context.
#### Simple teleportation
In a simple teleportation, the entity already exists in a world at a location
and the target location and dimension are known.
This operation is split into two parts: transform and async place.
In this case, the transform operation removes the entity from the current
world, then adjusts the position. The async place operation schedules a task
to the target location using the `RegionizedTaskQueue` to add the entity to
the target dimension at the target position.
The various implementation details such as non-player entities being
copied in the transform operation are left out, as those are not relevant
for the high level overview.
Things such as player login and player respawn are generally
considered simple teleportation. The player login case only differs
since the player does not exist in any world at the start, and that the async
transform must additionally find a place to spawn the player.
The player respawn is similar to the player login as the respawn
differs by having the player in the world at the time of respawn.
#### Portal teleport
Portal teleport differs from simple teleportation as portalling does
_not_ know the exact location of the teleport. Thus, the transform step
does not update the entity position, but rather a new operation is inserted
between transform and async place: an async search/create, which is responsible
for finding and/or creating the exit portal.
Additionally, the current Vanilla code can refuse a teleport if the
entity is non-player and the nether exit portal does not already exist. But
since the portal location is only determined by the async place, it is
too late to abort - so, the portal logic has been re-done so that there is no
difference between players and entities. Now both entities and players
create exit portals, whether it be for the nether or end.
#### Shutdown during teleport
Since the teleport happens over multiple steps, the server shutdown
process must deal with uncompleted teleportations manually.
## Server shutdown process
The shutdown process occurs by spawning a separate shutdown thread,
which then runs the shutdown logic:
1. Shutdown the tick region scheduler, stopping any further ticks
2. Halt metrics processing
3. Disable plugins
4. Stop accepting new connections
5. Send disconnect (but do not remove) packets to all players
6. Halt the chunk systems for all worlds
7. Execute shutdown logic for all worlds by finish all pending teleports
for all regions, then saving all chunks in the world, and finally
saving the level data for the world (level.dat and other .dat files).
8. Save all players
9. Shutting down the resource manager
10. Releasing the level lock
11. Halting remaining executors (Util executor, region I/O threads, etc.)
The important differences to Vanilla is that the player kick and
world saving logic is replaced by steps 5-8.
For step 5, the players cannot be kicked before teleportations are finished,
as kicking would save the player dat file. So, save is moved after.
For step 6, the chunk system halt is done before saving so that all chunk
generation is halted. This will reduce the load on the server as it shuts
down, which may be critical in memory-constrained scenarios.
For step 7, teleportations are completed differently depending on the type:
simple or portal.
Simple teleportations are completed by ensuring the addition
of the teleporting entity to the destination chunk specified
by teleportation. This allows the entity to be saved at the target
position, as if the teleportation did complete before shutdown.
Portal teleportations are completed by forcing the addition
of the teleporting entity to the source chunk, from where the entity should
have been teleported _from_. Since the target location is not known, the entity
can only be placed back at the origin (no teleportation). While this behavior
is not ideal, the shutdown logic _must_ account for any broken world state -
which means that finding or creating the target exit portal may not be an option.
The teleportation completion must be performed before the world save so that
the teleport completed entities save.
For step 8, only save players after the teleportations are completed.
The remaining steps are Vanilla.
---
# Region logic
An overview to how Folia's regionizer works.
## Fundamental regionizing logic
## Region
A region is simply a set of owned chunk positions and implementation
defined unique data object tied to that region. It is important
to note that for any non-dead region x, that for each chunk position y
it owns that there is no other non-dead region z such that
the region z owns the chunk position y.
## Regionizer
Each world has its own regionizer. The regionizer is a term used
to describe the logic that the class `ThreadedRegionizer` executes
to create, maintain, and destroy regions. Maintenance of regions is
done by merging nearby regions together, marking which regions
are eligible to be ticked, and finally by splitting any regions
into smaller independent regions. Effectively, it is the logic
performed to ensure that groups of nearby chunks are considered
a single independent region.
## Guarantees the regionizer provides
The regionizer provides a set of important invariants that allows
regions to tick in parallel without race conditions:
### First invariant
The first invariant is simply that any chunk holder that exists
has one, and only one, corresponding region.
### Second invariant
The second invariant is that for every _existing_ chunk holder x that is
contained in a region that every each chunk position within the
"merge radius" of x is owned by the region. Effectively, this invariant
guarantees that the region is not close to another region, which allows
the region to assume while ticking it can create data for chunk holders
"close" to it.
### Third invariant
The third invariant is that a ticking region _cannot_ expand
the chunk positions it owns as it ticks. The third invariant
is important as it prevents ticking regions from "fighting"
over non-owned nearby chunks, to ensure that they truly tick
in parallel, no matter what chunk loads they may issue while
ticking.
To comply with the first invariant, the regionizer will
create "transient" regions _around_ ticking regions. Specifically,
around in this context means close enough that would require a merge,
but not far enough to be considered independent. The transient regions
created in these cases will be merged into the ticking region
when the ticking region finishes ticking.
Both of the second invariant and third invariant combined allow
the regionizer to guarantee that a ticking region may create
and then access chunk holders around it (i.e. sync loading) without
the possibility that it steps on another region's toes.
### Fourth invariant
The fourth invariant is that a region is only in one of four
states: "transient", "ready", "ticking", or "dead."
The "ready" state allows a state to transition to the "ticking" state,
while the "transient" state is used as a state for a region that may
not tick. The "dead" state is used to mark regions which should
not be use.
The states transitions are explained later, as it ties in
with the regionizer's merge and split logic.
## Regionizer implementation
The regionizer implementation is a description of how
the class `ThreadedRegionizer` adheres to the four invariants
described previously.
### Splitting the world into sections
The regionizer does not operate on chunk coordinates, but rather
on "region section coordinates." Region section coordinates simply
represent a grouping of NxN chunks on a grid, where N is some power
of two. The actual number is left ambiguous, as region section coordinates
are only an internal detail of how chunks are grouped.
For example, with N=16 the region section (0,0) encompasses all
chunks x in [0,15] and z in [0,15]. This concept is similar to how
the chunk coordinate (0,0) encompasses all blocks x in [0, 15]
and z in [0, 15]. Another example with N=16, the chunk (17, -5) is
contained within region section (1, -1).
Region section coordinates are used only as a performance
tradeoff in the regionizer, as by approximating chunks to their
region coordinate allows it to treat NxN chunks as a single
unit for regionizing. This means that regions do not own chunks positions,
but rather own region section positions. The grouping of NxN chunks
allows the regionizing logic to be performed only on
the creation/destruction of region sections.
For example with N=16 this means up to NxN-1=255 possible
less operations in areas such as addChunk/region recalculation
assuming region sections are always full.
### Implementation variables
The implementation variables control how aggressively the
regionizer will maintain regions and merge regions.
#### Recalculation count
The recalculation count is the minimum number of region sections
that a region must own to allow it to re-calculate. Note that
a recalculation operation simply calculates the set of independent
regions that exist within a region to check if a split can be
performed.
This is a simple performance knob that allows split logic to be
turned off for small regions, as it is unlikely that small regions
can be split in the first place.
#### Max dead section percent
The max dead section percent is the minimum percent of dead
sections in a region that must exist before a region can run
re-calculation logic.
#### Empty section creation radius
The empty section creation radius variable is used to determine
how many empty region sections are to exist around _any_
region section with at least one chunk.
Internally, the regionizer enforces the third invariant by
preventing ticking regions from owning new region sections.
The creation of empty sections around any non-empty section will
then enforce the second invariant.
#### Region section merge radius
The merge radius variable is used to ensure that for any
existing region section x that for any other region section y within
the merge radius are either owned by region that owns x
or are pending a merge into the region that owns x or that the
region that owns x is pending a merge into the region that owns y.
#### Region section chunk shift
The region section chunk shift is simply log2(grid size N). Thus,
N = 1 << region section chunk shift. The conversion from
chunk position to region section is additionally defined as
region coordinate = chunk coordinate >> region section chunk shift.
### Operation
The regionizer is operated by invoking `ThreadedRegionizer#addChunk(x, z)`
or `ThreadedRegionizer#removeChunk(x, z)` when a chunk holder is created
or destroyed.
Additionally, `ThreadedRegion#tryMarkTicking` can be used by a caller
that attempts to move a region from the "ready" state to the "ticking"
state. It is vital to note that this function will return false if
the region is not in the "ready" state, as it is possible
that even a region considered to be "ready" in the past (i.e. scheduled
to tick) may be unexpectedly marked as "transient." Thus, the caller
needs to handle such cases. The caller that successfully marks
a region as ticking must mark it as non-ticking by using
`ThreadedRegion#markNotTicking`.
The function ThreadedRegion#markNotTicking returns true if the
region was migrated from "ticking" state to "ready" state, and false
in all other cases. Effectively, it returns whether the current region
may be later ticked again.
### Region section state
A region section state is one of "dead" or "alive." A region section
may additionally be considered "non-empty" if it contains
at least one chunk position, and "empty" otherwise.
A region section is considered "dead" if and only if the region section
is also "empty" and that there exist no other "empty" sections within the
empty section creation radius.
The existence of the dead section state is purely for performance, as it
allows the recalculation logic of a region to be delayed until the region
contains enough dead sections. However, dead sections are still
considered to belong to the region that owns them just as alive sections.
### Addition of chunks (`addChunk`)
The addition of chunks to the regionizer boils down to two cases:
#### Target region section already exists and is not empty
In this case, it simply adds the chunk to the section and returns.
#### Target region section does not exist or is empty
In this case, the region section will be created if it does not exist.
Additionally, the region sections in the "create empty radius" will be
created as well.
Then, any region in the create empty radius + merge radius are collected
into a set X. This set represents the regions that need to be merged
later to adhere to the second invariant.
If the set X contains no elements, then a region is created in the ready
state to own all of the created sections.
If the set X contains just 1 region, then no regions need to be merged
and no region state is modified, and the sections are added to this
1 region.
Merge logic needs to occur when there are more than 1 region in the
set X. From the set X, a region x is selected that is not ticking. If
no such x exists, then a region x is created. Every region section
created is added to the set x, as it is the section that is known
to not be ticking - this is done to adhere to the third invariant.
Every region y in the set X that is not x is merged into x if
y is not in the ticking state, otherwise x runs the merge later
logic into y.
### Merge later logic
A merge later operation may only take place from
a non-ticking, non-dead region x into a ticking region y.
The merge later logic relies on maintaining a set of regions
to merge into later per region, and another set of regions
that are expected to merge into this region.
Effectively, a merge into later operation from x into y will add y into x's
merge into later set, and add x into y's expecting merge from set.
When the ticking region finishes ticking, the ticking region
will perform the merge logic for all expecting merges.
### Merge logic
A merge operation may only take place between a dead region x
and another region y which may be either "transient"
or "ready." The region x is effectively absorbed into the
region y, as the sections in x are moved to the region y.
The merge into later is also forwarded to the region y,
such so that the regions x was to merge into later, y will
now merge into later.
Additionally, if there is implementation specific data
on region x, the region callback to merge the data into the
region y is invoked.
The state of the region y may be updated after a merge operation
completes. For example, if the region x was "transient", then
the region y should be downgraded to transient as well. Specifically,
the region y should be marked as transient if region x contained
merge later targets that were not y. The downgrading to transient is
required to adhere to the second invariant.
### Removal of chunks (`removeChunk`)
Removal of chunks from region sections simple updates
the region sections state to "dead" or "alive", as well as the
region sections in the empty creation radius. It will not update
any region state, and nor will it purge region sections.
### Region tick start (`tryMarkTicking`)
The tick start simply migrates the state to ticking, so that
invariants #2 and #3 can be met.
### Region tick end (`markNotTicking`)
At the end of a tick, the region's new state is not immediately known.
First, the region must process its pending merges.
After it processes its pending merges, it must then check if the
region is now pending merge into any other region. If it is, then
it transitions to the transient state.
Otherwise, it will process the removal of dead sections and attempt
to split into smaller regions. Note that it is guaranteed
that if a region can be possibly split, it must remove dead sections,
otherwise, this would contradict the rules used to build the region
in the first place.
---
# Welcome
Documentation for all projects under the PaperMC umbrella, including Paper, Velocity and Folia.
import { CardGrid } from "@astrojs/starlight/components";
import { ContributorList } from "astro-contributors";
import LinkCard from "/src/components/LinkCard.astro";
High performance Minecraft server that aims to fix gameplay and mechanics inconsistencies.
The modern, next-generation Minecraft server proxy.
A fork of Paper which adds regionized multithreading to the dedicated server.
A Java library for server-controllable user interface elements in Minecraft: Java Edition.
A discontinued BungeeCord proxy fork that aimed to improve performance and stability.
Various other documentation and tools.
## [Contribute](https://github.com/PaperMC/docs)
We’d like to extend a warm thank you to all the contributors who have helped bring this documentation site to life.
Whether you've written code, submitted feedback, fixed a typo, or shared your expertise - every contribution is deeply appreciated.
This is very much a team effort, and we’re grateful to have you with us on the journey!
---
# Miscellaneous
import PageCards from "/src/components/PageCards.astro";
Documentation that does not cleanly fit in any other category.
---
# Art assets
The official PaperMC and Velocity logomarks and the terms under which you may use them.
This page provides the official PaperMC and Velocity logomarks and the terms under which you may use them.
Images on this page are available through our CDN, and using the provided URLs is encouraged (but not necessary)
when referencing these assets in your projects, as long as you adhere to the usage guidelines outlined below.
:::caution
The logomarks are subject to their own separate licensing terms and do not inherit any from the
projects they represent.
:::
## PaperMC
You may:
- Use the PaperMC logomark to represent the project in blogposts and other places in order to bring
attention to the project.
- Use the PaperMC logomark to represent Paper-Server in downloads, server selectors, and similar
places.
- Crop out extra transparent canvas space behind the PaperMC logomark, so it fits better next to
other content.
You may not:
- Alter any of the colors used in the PaperMC logomark.
- Change the dimensions of the PaperMC logomark.
- Create modified versions of the PaperMC logomark or derivative works of it.
- Add your own project images or branding to the PaperMC logomark.
- Claim the logomark as your own work or use it as a representation of your own projects.
- Sell the PaperMC logomark on its own or as part of other products without explicit permission.
- Alter the transparency of any elements within the PaperMC logomark.
| Image | URL |
|----------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
| 
| `https://assets.papermc.io/brand/papermc_combination_mark_dark.min.svg` |
| 
| `https://assets.papermc.io/brand/papermc_combination_mark_light.min.svg` |
|  | `https://assets.papermc.io/brand/papermc_logo.min.svg` |
|  | `https://assets.papermc.io/brand/papermc_logo.256.png` |
|  | `https://assets.papermc.io/brand/papermc_logo.512.png` |
## Velocity
Please do not edit, recolor, rearrange, or distort the Velocity logo. Resizing the
logo and cropping out any blank space is acceptable.
The logo should not be used in a matter that suggests the Velocity project officially
endorses some product or service. For instance, you may advertise a plugin as being
compatible with Velocity, but you may not make the Velocity logo prominent in that
advertising.
| Image | URL |
|-----------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
|  | `https://assets.papermc.io/brand/velocity_combination_mark_blue.min.svg` |
| 
| `https://assets.papermc.io/brand/velocity_combination_mark_white.min.svg` |
|  | `https://assets.papermc.io/brand/velocity_logo_blue.min.svg` |
| 
| `https://assets.papermc.io/brand/velocity_logo_white.min.svg` |
---
# Contact us
There are many ways to contact us, see here for more information.
## Discord
The PaperMC project handles most communication via Discord. Use the following invite: https://discord.gg/papermc
## Forums
Reach out for support, or contact us on our forums.
- [Forums](https://forums.papermc.io)
## Twitter/X
We often tweet out version release notes, update notices, and other information via our Twitter
page.
- [@PaperPowered](https://x.com/PaperPowered)
You should not DM or @ this account for support. It is not checked as regularly as the above
locations.
---
# Downloads Service
Paper provides a downloads service that you can use to access builds.
PaperMC provides a downloads service to facilitate automated downloads access. Full documentation can be
found on the [Downloads Service Docs](https://fill.papermc.io/swagger-ui/index.html#/).
:::danger
We emphatically **do not recommend** using unstable builds or auto-updaters within production environments.
:::
All requests must now include a valid User-Agent header that:
- Clearly identifies your software or company
- Is not generic (e.g. curl, wget, or similar defaults)
- Includes a contact URL or email address (e.g. a homepage, bot info page, or support email)
**Some examples:**
```
mc-image-helper/1.39.11 (https://github.com/itzg/docker-minecraft-server)
nodecraft/packifier/1.0.0 (staff@nodecraft.com)
```
## REST API examples
:::note
We require `jq` to be installed for the examples below. You can install it with `sudo apt-get install jq` on Debian/Ubuntu.
:::
### Getting the latest version
```shell
#!/usr/bin/env sh
PROJECT="paper"
USER_AGENT="cool-project/1.0.0 (contact@me.com)"
LATEST_VERSION=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT} | \
jq -r '.versions | to_entries[0] | .value[0]')
echo "Latest version is $LATEST_VERSION"
```
This will get the latest available Minecraft version for the given project.
### Getting the latest stable build number
```shell replace
#!/usr/bin/env sh
PROJECT="paper"
MINECRAFT_VERSION="\{LATEST_PAPER_RELEASE}"
USER_AGENT="cool-project/1.0.0 (contact@me.com)"
LATEST_BUILD=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT}/versions/${MINECRAFT_VERSION}/builds | \
jq -r 'map(select(.channel == "STABLE")) | .[0] | .id')
if [ "$LATEST_BUILD" != "null" ]; then
echo "Latest stable build is $LATEST_BUILD"
else
echo "No stable build for version $MINECRAFT_VERSION found :("
fi
```
This will get the latest stable build for the given project and Minecraft version, if it's available.
### Downloading the latest stable build
```shell replace
#!/usr/bin/env sh
PROJECT="paper"
MINECRAFT_VERSION="\{LATEST_PAPER_RELEASE}"
USER_AGENT="cool-project/1.0.0 (contact@me.com)"
# First check if the requested version has a stable build
BUILDS_RESPONSE=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT}/versions/${MINECRAFT_VERSION}/builds)
# Check if the API returned an error
if echo "$BUILDS_RESPONSE" | jq -e '.ok == false' > /dev/null 2>&1; then
ERROR_MSG=$(echo "$BUILDS_RESPONSE" | jq -r '.message // "Unknown error"')
echo "Error: $ERROR_MSG"
exit 1
fi
# Try to get a stable build URL for the requested version
PAPERMC_URL=$(echo "$BUILDS_RESPONSE" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // "null"')
FOUND_VERSION="$MINECRAFT_VERSION"
# If no stable build for requested version, find the latest version with a stable build
if [ "$PAPERMC_URL" == "null" ]; then
echo "No stable build for version $MINECRAFT_VERSION, searching for latest version with stable build..."
# Get all versions for the project (using the same endpoint structure as the "Getting the latest version" example)
# The versions are organized by version group, so we need to extract all versions from all groups
# Then sort them properly as semantic versions (newest first)
VERSIONS=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT} | \
jq -r '.versions | to_entries[] | .value[]' | \
sort -V -r)
# Iterate through versions to find one with a stable build
for VERSION in $VERSIONS; do
VERSION_BUILDS=$(curl -s -H "User-Agent: $USER_AGENT" https://fill.papermc.io/v3/projects/${PROJECT}/versions/${VERSION}/builds)
# Check if this version has a stable build
STABLE_URL=$(echo "$VERSION_BUILDS" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // "null"')
if [ "$STABLE_URL" != "null" ]; then
PAPERMC_URL="$STABLE_URL"
FOUND_VERSION="$VERSION"
echo "Found stable build for version $VERSION"
break
fi
done
fi
if [ "$PAPERMC_URL" != "null" ]; then
# Download the latest Paper version
curl -o server.jar $PAPERMC_URL
echo "Download completed (version: $FOUND_VERSION)"
else
echo "No stable builds available for any version :("
exit 1
fi
```
This is the most common use case for the API. It will download the latest stable build for the given project and
Minecraft version. You should always serve & use the stable builds. Experimental builds are prone to error and
do not receive support.
## GraphQL API examples
Fill also supports a GraphQL API, which can be accessed at `https://fill.papermc.io/graphql`.
Fill's GraphQL API uses standard pagination, which you can learn more about [here](https://graphql.org/learn/pagination/).
A built-in GraphQL playground is available at https://fill.papermc.io/graphiql?path=/graphql.
Common API tools such as Postman will introspect the API and provide a UI for building queries.
### Getting the latest version
```graphql
query LatestVersion {
project(key: "paper") {
key
versions(first: 1, orderBy: {direction: DESC}) {
edges {
node {
key
}
}
}
}
}
```
Example response
```json
{
"data": {
"project": {
"key": "paper",
"versions": {
"edges": [
{
"node": {
"key": "1.21.11"
}
}
]
}
}
}
}
```
### Getting the latest stable build number
```graphql
query LatestStableBuild {
project(key: "paper") {
key
versions(first: 1, orderBy: {direction: DESC}) {
edges {
node {
key
builds(filterBy: { channel: STABLE }, first: 1, orderBy: { direction: DESC }) {
edges {
node {
number
}
}
}
}
}
}
}
}
```
Example response
```json
{
"data": {
"project": {
"key": "paper",
"versions": {
"edges": [
{
"node": {
"key": "1.21.10",
"builds": {
"edges": [
{
"node": {
"number": 48
}
}
]
}
}
}
]
}
}
}
}
```
### Getting the latest stable build download URL
```graphql
query LatestStableBuildDownloadURL {
project(key: "paper") {
key
versions(first: 1, orderBy: {direction: DESC}) {
edges {
node {
key
builds(filterBy: { channel: STABLE }, first: 1, orderBy: { direction: DESC }) {
edges {
node {
number
download(key: "server:default") {
name
url
checksums {
sha256
}
size
}
}
}
}
}
}
}
}
}
```
Example response
```json
{
"data": {
"project": {
"key": "paper",
"versions": {
"edges": [
{
"node": {
"key": "1.21.10",
"builds": {
"edges": [
{
"node": {
"number": 48,
"download": {
"name": "paper-1.21.10-48.jar",
"url": "https://fill-data.papermc.io/v1/objects/bfca155b4a6b45644bfc1766f4e02a83c736e45fcc060e8788c71d6e7b3d56f6/paper-1.21.10-48.jar",
"checksums": {
"sha256": "bfca155b4a6b45644bfc1766f4e02a83c736e45fcc060e8788c71d6e7b3d56f6"
},
"size": 54185955
}
}
}
]
}
}
}
]
}
}
}
}
```
---
# Hangar auto-publishing
How to automatically publish your plugin to Hangar on commits.
If you want to automatically publish your plugin to [Hangar](https://hangar.papermc.io/) on pushes, you can use
our [Gradle plugin](https://github.com/HangarMC/hangar-publish-plugin).
After you have added the required `hangarPublish` configuration, you can manually publish it by
running `./gradlew build publishPluginPublicationToHangar`, or have GitHub Actions automatically publish a version on
every commit.
## Prerequisites
### Gradle
Your plugin project needs to use Gradle as its build tooling.
:::tip
If you are using
Maven, [switching to a Gradle setup is easy](https://docs.gradle.org/current/userguide/migrating_from_maven.html) and in
general recommended due to higher configurability and support for other plugins, such as
when [compiling against an unobfuscated Minecraft server](/paper/dev/userdev).
The provided examples use Kotlin DSL, but you can also do the same using Groovy. Online converters (even ChatGPT)
are able to convert the example code.
:::
### Creating the `Snapshot` release channel
The builds script below will publish non-release builds under a `Snapshot` channel. You need to create this channel in
your Hangar project's channel page first.

### Adding the `HANGAR_API_TOKEN` repository secret
First, you need to create a Hangar API token. Go to your Hangar settings in the profile dropdown and click on "Api keys" on the left. Then, tick
the `create_version` permission box, give the key a name and create it. **At the top**, you should be given your secret API
token. **Do not share this with anyone**; you will need it in the next step.
The GitHub Actions workflow provided uses a repository secret to store your Hangar API key. Go to your GitHub project settings,
then click on "Actions" under the Security tab and click the "New repository secret" button. Name the
secret `HANGAR_API_TOKEN` and paste the Hangar API token from the previous step into the Secret field.

## Project files
The files below are simple examples that require little manual changes for you to use, but you can still adapt them
depending on your needs. Take a look at the comments and especially the TODOs to figure out what you still need to
change.
### `gradle.properties`
Create a `gradle.properties` file in your project root directory if it does not already exist. In there, you define the
platform versions your plugin is compatible with. Simply remove the platforms you don't need and put in the correct
versions.
Hangar allows version ranges (such as `1.19-1.20.2`) and wildcards (such as `1.20.x`).
```properties
# Specify the platform versions for Paper and Velocity.
# Hangar also allows version ranges (such as 1.19-1.20.2) and wildcards (such as 1.20.x).
# TODO: Remove the platforms you don't need and put in the correct versions.
paperVersion=1.12.2, 1.16.5, 1.19-1.20.2
velocityVersion=3.2
waterfallVersion=1.20
```
### `build.gradle.kts`
In the plugins block of your `build.gradle.kts` build script, add the publish plugin:
```kotlin title="build.gradle.kts"
plugins {
id("io.papermc.hangar-publish-plugin") version "0.1.2"
}
```
Then you simply need to add the `hangarPublish` configuration block and make sure you do the following:
- If your plugin is not a Paper plugin, or supports Velocity/Waterfall as well, copy the register block with a different
platform and change the property used instead of `paperVersion` (as declared in the `gradle.properties` file).
- Insert the correct project namespace
- Insert your plugin dependencies, if any
- You need to have the `HANGAR_API_TOKEN` repository secret set up if you are using the Actions file below, otherwise add the
API key in some other way.
```kotlin
import io.papermc.hangarpublishplugin.model.Platforms
// ...
hangarPublish {
publications.register("plugin") {
version.set(project.version as String)
channel.set("Snapshot") // We're using the 'Snapshot' channel
// TODO: Edit the project name to match your Hangar project
id.set("hangar-project")
apiKey.set(System.getenv("HANGAR_API_TOKEN"))
platforms {
// TODO: Use the correct platform(s) for your plugin
register(Platforms.PAPER) {
// TODO: If you're using ShadowJar, replace the jar lines with the appropriate task:
// jar.set(tasks.shadowJar.flatMap { it.archiveFile })
// Set the JAR file to upload
jar.set(tasks.jar.flatMap { it.archiveFile })
// Set platform versions from gradle.properties file
val versions: List = (property("paperVersion") as String)
.split(",")
.map { it.trim() }
platformVersions.set(versions)
// TODO: Configure your plugin dependencies, if any
dependencies {
// Example for a dependency found on Hangar
hangar("Maintenance") {
required.set(false)
}
// Example for an external dependency
url("Debuggery", "https://github.com/PaperMC/Debuggery") {
required.set(true)
}
}
}
}
}
}
```
## GitHub Actions workflow
You don't necessarily need to publish via GitHub Actions, but it is an easy way to do so. If you want to use it, create
a `publish.yml` file in the `.github/workflows` directory of your project root folder and make sure
you [add the repository secret](#adding-the-hangar_api_token-repository-secret).
You can add and remove branches to be published by editing the `branches` section.
```yaml
name: Publish to Hangar
on:
push:
branches:
# Add any additional branches you want to automatically publish from
- main # Assuming your main branch is called 'main'
jobs:
publish:
# TODO: Optional, make sure the task only runs on pushes to your repository and doesn't fail on forks. Uncomment the line below and put the repo owner into the quotes
# if: github.repository_owner == ''
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Set up JDK 25
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 25
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Publish
env:
# Make sure you have added a repository secret in the repository's settings
HANGAR_API_TOKEN: ${{ secrets.HANGAR_API_TOKEN }}
run: ./gradlew build publishPluginPublicationToHangar --stacktrace
```
## Optional: Handling multiple channels and an automatic changelog
With the following code, any version that contains a hyphen (`-`) will be published under the `Snapshot` channel
(that you need to create on Hangar) and the others on the `Release` channel.
By editing the `channel.set(...)` line, you can change this to any channel you would like.
For example, you could further split builds depending on the branch you are currently on into `Alpha` builds.
:::caution
Make sure you never publish ongoing development builds to the `Release` channel.
:::
```kotlin
import java.io.ByteArrayOutputStream
// ...
// Helper methods
fun executeGitCommand(vararg command: String): String {
val byteOut = ByteArrayOutputStream()
exec {
commandLine = listOf("git", *command)
standardOutput = byteOut
}
return byteOut.toString(Charsets.UTF_8.name()).trim()
}
fun latestCommitMessage(): String {
return executeGitCommand("log", "-1", "--pretty=%B")
}
val versionString: String = version as String
val isRelease: Boolean = !versionString.contains('-')
val suffixedVersion: String = if (isRelease) {
versionString
} else {
// Give the version a unique name by using the GitHub Actions run number
versionString + "+" + System.getenv("GITHUB_RUN_NUMBER")
}
// Use the commit description for the changelog
val changelogContent: String = latestCommitMessage()
// If you would like to publish releases with their proper changelogs manually, simply add an if statement with the `isRelease` variable here.
hangarPublish {
publications.register("plugin") {
version.set(suffixedVersion)
channel.set(if (isRelease) "Release" else "Snapshot")
changelog.set(changelogContent)
// ... (see above)
}
}
```
## Optional: Updating the resource page
A notable part of publishing a new version might be updating the resource page (e.g. the plugin's home page) with new content from your plugin's repository.
In this example, we're using a README file, but you can use any text you want, as long as it is in Markdown format.
```kotlin
val pageContent = project.file("README.md").readText()
hangarPublish {
publications.register("plugin") {
// ... (see above)
pages.resourcePage(pageContent)
}
}
```
You can invoke the `syncPluginPublicationMainResourcePagePageToHangar` task to update the resource page on Hangar.
This will not publish a new version, but simply update the page content on Hangar.
If you're using a GitHub Actions workflow, you should also add the task invocation to the `Publish` step in your workflow:
```yaml
- name: Publish
# ...
run: ./gradlew build publishPluginPublicationToHangar syncPluginPublicationMainResourcePagePageToHangar --stacktrace
```
---
# Installing or updating Java
How to install or update to Java 25 on Linux (apt/rpm), Windows, or Mac.
Installing Java is a critical first step to using or developing plugins for Paper and Velocity.
This guide will walk you through the recommended installation steps for most major
platforms.
:::caution[Do not use headless variants of Java!]
There are `headless` variants of Java which usually have a suffix of `-headless` in their package
name. Those variants miss required dependencies for Paper. Therefore, using them is not recommended.
:::
:::tip
This guide focuses on Amazon's Corretto OpenJDK distribution. This is because it offers the best
installation experience on the most platforms. Corretto is, however, not the only OpenJDK vendor to
choose from. Many alternatives exist such as [Eclipse Adoptium](https://adoptium.net/),
[Microsoft](https://www.microsoft.com/openjdk) and
[Azul Zulu](https://www.azul.com/downloads/?package=jdk). Note that the JDK Oracle distributes,
while functionally identical, is **not** recommended due to an extremely unfriendly installer and
previous hostile licensing.
:::
## Linux
### Ubuntu/Debian
Installing Java 25 on Debian-based Linux distributions is very simple. First, ensure your system has
all required tools to successfully install Java.
```bash
sudo apt-get update
sudo apt-get install ca-certificates apt-transport-https gnupg wget
```
Second, import the Amazon Corretto public key and apt repository.
```bash
wget -O - https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg && \
echo "deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main" | sudo tee /etc/apt/sources.list.d/corretto.list
```
Then, install Java 25 and other dependencies using the following command:
```bash
sudo apt-get update
sudo apt-get install -y java-25-amazon-corretto-jdk libxi6 libxtst6 libxrender1
```
Proceed to [verify your installation](#verifying-installation).
### RPM-based
To install Java 25 on CentOS, RHEL, Fedora, openSUSE, SLES, or any other RPM-based Linux
distribution, execute the following commands depending on your package manager. Once you have
finished, precede to [verify your installation](#verifying-installation).
#### DNF
DNF is used on Fedora, CentOS/RHEL 7+, and related distributions.
```bash
sudo rpm --import https://yum.corretto.aws/corretto.key
sudo curl -Lo /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo
sudo dnf -y install java-25-amazon-corretto-devel
```
#### Zypper
Zypper is used on openSUSE, SLES, and related distributions.
```bash
sudo zypper addrepo https://yum.corretto.aws/corretto.repo
sudo zypper refresh
sudo zypper install java-25-amazon-corretto-devel
```
#### YUM
YUM is used on older releases of CentOS/RHEL, and excessively old releases of Fedora.
```bash
sudo rpm --import https://yum.corretto.aws/corretto.key
sudo curl -Lo /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo
sudo yum -y install java-25-amazon-corretto-devel
```
## Windows 10 & 11
If you're on Windows 10 or 11, installing Java is just like installing any other program. Download
the Amazon Corretto installer from
[their website](https://corretto.aws/downloads/latest/amazon-corretto-25-x64-windows-jdk.msi).
Once you have run the installer, it is safe to click "next" through the whole process. No additional
bloatware or toolbars will be installed, and all the required features are enabled out of the box.
Now, open a command prompt and precede to [verify your installation](#verifying-installation).
## macOS/OS X
If you're on macOS, the best way to manage Java installations is with a tool called
[Homebrew](https://brew.sh). Follow the instructions on their homepage to install it. Then, in your
terminal run the following command:
```bash
brew install openjdk@25
```
Once this command has completed, continue to [verify your installation](#verifying-installation).
## Pterodactyl
:::note
On Pterodactyl versions lower than `1.2.0`, an administrator account is required to change the Java
version. These instructions will not apply.
:::
If you have started a Paper server with an incorrect Java version, Pterodactyl will automatically
prompt you to update like this:

If this does not show up for you, the Java version can be manually changed. Navigate to the
"Startup" tab of your server, select `Java 25` from the "Docker Image"
dropdown as shown in the image below.

:::note
If you don't see `Java 25` in the dropdown, an administrator account is required to update the Paper egg.
:::
## Pufferpanel (Docker)
:::note
You will need **Admin** permissions on the panel to change your Java version.
:::
At the server you want to change the Java version for, go to the **Admin** tab and open
**Edit Server Definition**. In the **Environment** tab, the **Docker Image** field allows
you edit the docker image used.

In this case, you can simply replace the version (the number after the ":") with `25`.
You can find alternative JDK docker images on
[Docker Hub](https://hub.docker.com/search?badges=official).
## Verifying installation
:::note
This section does not apply when running on Pterodactyl or Pufferpanel.
:::
Now that you have installed Java 25, run this command in your terminal to ensure the process was
successful.
```bash
java -version
```
The output should be similar to this. The important parts to look out for is that it starts with
`openjdk 25` and contains `64-Bit` in the last line. If the output you get is similar to
`java: command not found`, try creating a new terminal session.
```
openjdk version "25" 2025-09-16
OpenJDK Runtime Environment (build 25+36-3489)
OpenJDK 64-Bit Server VM (build 25+36-3489, mixed mode, sharing)
```
If your installation has failed, do not hesitate to reach out in the `#paper-help` channel of our
[Discord](https://discord.gg/papermc) for support.
---
# Tools
import PageCards from "/src/components/PageCards.astro";
Collection of useful tools for server admins or plugin developers.
---
# Diff viewer
Multi-file rich diff viewer for GitHub and diff/patch files.
Multi-file rich diff viewer with support for GitHub commits, PRs, and comparisons, as well as
diff and patch files.
The GitHub diff viewer is a great tool for most use cases, but it has a variety of limitations,
especially when dealing with large diffs where the viewer will perform poorly or even crash.
The diffs.dev Diff Viewer aims to solve this problem while also providing a variety of additional
features useful for PaperMC and generally, such as special handling for second-order diffs. We have
also built a browser extension for Firefox and Chrome to streamline opening GitHub diffs in the
viewer.
- https://diffs.dev - The diff viewer itself
- [Chrome Extension](https://chromewebstore.google.com/detail/patch-roulette/feaaoepdocmiibjilhoahgldkaajfnhb)
- [Firefox Add-on](https://addons.mozilla.org/en-US/firefox/addon/patch-roulette/)
- https://github.com/PaperMC/diff-viewer - GitHub repository
---
# Item command converter
A tool to update commands from the old NBT-based item format to item components.
import ItemCommandConverter from "/src/components/tools/ItemCommandConverter.svelte";
In 1.20.5, Mojang has moved from unstructured NBT to the so-called item components. While Minecraft
will automatically update all existing items for you, only Paper will update commands and text
components containing items where possible. This tool helps you convert old item commands to the
new format externally.
Note that you need to select the *Entity Argument* mode if you want to convert entity data outside of
the summon command, as we require the entity type to convert data correctly.
---
# MiniMessage web editor
A web-based editor for creating and previewing MiniMessage-formatted text.
The MiniMessage Web Editor is a web-based editor for creating and previewing MiniMessage-formatted text.
It is hosted here: [MiniMessage Web Editor](https://webui.advntr.dev/).

---
# Start script generator
A start script generator for PaperMC projects.
import StartScriptGenerator from "/src/components/tools/StartScriptGenerator.svelte";
What flags do I choose?
If you're running on a modern JVM, i.e. 17, 21 or higher, try with no flags first.
Modern JVMs are very good at handling various applications with default GC settings.
If you have GC problems or are running an older version of Minecraft/an older version of Java,
try [Aikar's flags](/paper/aikars-flags), which are optimized specifically for Minecraft.
If you're generating a script for starting [Velocity](https://papermc.io/software/velocity), choose "Velocity".
---
# Paper Documentation
import { CardGrid, LinkCard } from "@astrojs/starlight/components";
Paper is a Minecraft: Java Edition game server, designed to greatly improve performance and offer more advanced features and API.
---
# Adding plugins
Plugins are the most powerful way to extend the functionality of Paper beyond the configuration files.
Plugins are the most powerful way to extend the functionality of Paper beyond the configuration
files. Functionality added by plugins can range from making milk restore hunger or dead bushes grow,
to adding entirely new and original game modes or items.
:::danger[Malicious Plugins]
Ensure you fully trust the source of any plugin before installing it. Plugins are given **full and
unrestricted** access to not only your server but also the machine that it runs on. Because of this,
it is imperative that plugins only be installed from trusted sources. Be careful!
:::
## Finding plugins
Before installing a plugin, you'll need to find what you want to install. The best place to find
plugins is [Hangar](https://hangar.papermc.io), Paper's plugin repository, but you can also find
plugins on [Modrinth](https://modrinth.com/discover/plugins?g=categories:paper) or
[BukkitDev](https://dev.bukkit.org/bukkit-plugins). Many plugins may release on [GitHub](https://github.com).
Instead of checking each site directly, you can also use a search engine. Searching for the function you
desire, followed by `Paper plugin` will often yield good results.
## Installing plugins
1. Once you've found the plugin you'd like to install, download it. Ensure the file you have
downloaded ends in `.jar`. Some plugins also distribute as `.zip` files, in which case you will
need to extract the file and locate the `.jar` for your platform, often labeled `bukkit` or
`paper`.
2. Once you have the plugin downloaded locally, locate the `plugins` folder from the root directory
of your Paper server.
3. Drag and drop the plugin file (`.jar`) into the `plugins` folder. If you are using a shared
hosting service, you may need to use their web panel or SFTP to upload the plugin; however, the
procedure will be the same.
4. Restart your server. The plugin should load.
5. Check your work. Once the server has finished loading, run the `/plugins` command in-game or type
`plugins` into the console. You should see your freshly installed plugin listed in green. If it
is not listed or is colored red, continue to [troubleshooting](#troubleshooting). A plugin listed
in red means that it is not currently enabled. For a freshly installed plugin, this often means
that the plugin failed to load.
## Troubleshooting
### Check your logs
The first step to troubleshooting installing plugins is to check the log of your server. Your
server's most recent logs will be stored to the `logs/latest.log` file. You may need to scroll near
the beginning of this file to see when plugins were loaded.
#### Missing dependencies
If you see something like this:
```log
[00:00:00] [Server thread/WARN] Could not load 'plugins/MyAwesomePlugin-1.0.0.jar' in folder 'plugins'
[00:00:00] [Server thread/WARN] org.bukkit.plugin.UnknownDependencyException: Unknown/missing dependency plugins: [Vault]. Please download and install these plugins to run 'MyAwesomePlugin'.
```
This means that the plugin you tried to install is missing a dependency. A dependency, in this case,
is another plugin that you must install for the first to function. While you will get a big scary
error, the important line to look at is:
```log
[00:00:00] [Server thread/WARN] Unknown/missing dependency plugins: [Vault]. Please download and install these plugins to run 'MyAwesomePlugin'.
```
This is telling you that in order to load `MyAwesomePlugin`, you must first install `Vault`.
#### Invalid `plugin.yml`
If you see something closer to this:
```log
[00:00:00] [Server thread/WARN] Could not load 'plugins/MyAwesomePlugin-1.0.0.jar' in folder 'plugins'
[00:00:00] [Server thread/WARN] org.bukkit.plugin.InvalidDescriptionException: Invalid plugin.yml
```
This means that what you have downloaded isn't a valid Paper plugin. This is generally caused by one
of the following:
1. The plugin you downloaded isn't a plugin at all, but instead a mod for Forge, Fabric, or similar.
These will not run on Paper.
2. The plugin failed to download completely. Especially when using tools such as `curl` or `wget`,
you can easily download error pages rather than the plugin you intended. This may also be caused
by a network issue. Attempt to download the plugin again. If you are using FTP (not SFTP or a web
panel) to upload your plugin to a shared hosting service, ensure your FTP client is in `binary`
and not `ASCII` mode. Consult the documentation for your FTP client for details.
#### Ambiguous plugin name
If you see something like this:
```log
[00:00:00] [Server thread/WARN] Ambiguous plugin name `Essentials' for files `plugins/EssentialsX-2.19.4.jar' and `plugins/Essentialsx-2.20.0-dev.jar' in `plugins'
```
This means you have two plugins with the same name, which is not supported. In this case, two
versions of EssentialsX are installed. Both the release `2.19.4`, and a development build of
`2.20.0`. Ensure you only have one version of each plugin installed at one time. Delete the older
version of the duplicate plugin, and restart your server.
To prevent accidentally installing two versions of one plugin while updating, you can use
the `update` folder as described in the [Update Guide](/paper/updating#step-2-update-plugins).
#### Something else
If you see an error, but it isn't similar to one of the above, attempt to read it yourself. While
the full error may be large and scary, you likely only have to read the first one or two lines to
understand what is going on. If you're not sure, do not hesitate to reach out for support on our
[Discord](https://discord.gg/papermc) in the `#paper-help` channel.
### If nothing is logged
If nothing is logged, your server is likely not attempting to load any plugins. The conditions
needed for the server to load a plugin are as follows:
1. The file is at the root of the `plugins` folder, relative to its working directory. This is
usually the same folder as the server JAR file. **Subdirectories of the `plugins` folder will not
be checked.** All plugins must be in the root folder.
2. The file ends in `.jar`. If your plugin does not end in `.jar`, what you have downloaded may not
be a plugin. Note that some plugins distribute multiple JARs as `.zip` files. If this is the
case, you have to extract them before installing the plugin.
If both of these are true, and you still see no logs, please reach out for support on our
[Discord](https://discord.gg/papermc) server in the `#paper-help` channel. We will be happy to
assist you.
---
# Administration
import PageCards from "/src/components/PageCards.astro";
Welcome to the Paper administration guide! This guide includes information and tutorials
regarding the administration of a Paper server.
#### Getting started
#### How-to guides
#### Reference
#### Miscellaneous
---
# Getting started
import PageCards from "/src/components/PageCards.astro";
Guides for getting started with running a Paper server.
---
# How-to guides
import PageCards from "/src/components/PageCards.astro";
How-to guides for setting up specific features of Paper and managing the server.
---
# Miscellaneous
import PageCards from "/src/components/PageCards.astro";
Miscellaneous documentation pertaining to Paper.
---
# Reference
import PageCards from "/src/components/PageCards.astro";
Reference of Paper's configuration options, system properties and other features.
---
# Aikar's flags
Aikar's flags are a set of JVM flags designed to improve the performance of your Paper server.
## Recommended JVM startup flags
:::caution[Script Generator]
**This page only serves as an explanation page.** If you want to generate a start script, please visit
our **[Script Generator](/misc/tools/start-script-gen)**.
:::
```bash
java -Xms10G -Xmx10G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true -jar paper.jar --nogui
```
:::danger[Do not allocate all of your available memory on a shared host!]
When setting the `Xms` and `Xmx` values, if your host says you have 8GB of memory, **do not use 8GB**!
Minecraft (and Java) needs additional memory on top of that `Xmx` parameter. It is recommended to
**reduce your `Xmx` and `Xms` by about 1000-1500MB** to avoid running out of memory or `OOMKiller` killing
your server. This also leaves room for the operating system to use memory too.
Do you have 8GB of memory? Use 6500MB for safety.
_But you may also ask your host if they will cover this overhead for you and
give you 9500M instead. Some hosts will! Just ask._
:::
## Recommended memory
**We recommend using at least 6-10GB**, no matter how few players! If you can't afford 10GB of
memory, give as much as you can, but ensure you leave the operating system some memory too. G1GC
operates better with more memory.
However, more memory does not mean better performance above a certain point. Eventually you will hit
a point of diminishing returns. Going out and getting 32GB of RAM for a server will only waste your
money with minimal returns.
## Java GC logging
Are you having old gen issues with these flags? Add the following flags based on your Java version
to enable GC logging:
**Java 8-10**
```bash
-Xloggc:gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=1M
```
**Java 11+**
```bash
-Xlog:gc*:logs/gc.log:time,uptime:filecount=5,filesize=1M
```
GC logging does not hurt your performance and can be left on at all times. The files will not take
up much space (5MB)
## Technical explanation of the flags
1. **-Xms matching -Xmx - why:** You should never run your server with the case that `Xmx` can run
the system completely out of memory. Your server should always be expected to use the entire
`Xmx`! You should then ensure the OS has extra memory on top of that `Xmx` for non-Minecraft/OS level
things. Therefore, you should never run Minecraft with `Xmx` settings you can't support if Java uses it
all. Now, that means **if `Xms` is lower than `Xmx` you have unused memory**! Unused memory is
wasted memory. G1 operates better with the more memory it's given. G1 adaptively chooses how
much memory to give to each region to optimize pause time. If you have more memory than it needs
to reach an optimal pause time, G1 will simply push that extra into the old generation, and it
will not hurt you. The fundamental idea of improving GC behavior is to ensure short-lived objects
die young and never get promoted. With the more memory G1 has, the better assurance you will get
that objects are not getting prematurely promoted to the old generation. G1 operates differently
than previous collectors and is able to handle larger heaps more efficiently.
If it does not need the memory given to it, it will not use it. The entire engine operates
differently and does not suffer from too large of heaps, and this is industry-wide accepted
information that under G1 to keep Xms and Xmx the same!
2. **UnlockExperimentalVMOptions:** needed for some the below options
3. **G1NewSizePercent:** These are the important ones. You now can specify percentages of an
overall desired range for the new generation. With these settings, we tell G1 to not use its
default 5% for new gen, and instead give it 40%! **Minecraft has an extremely high memory
allocation rate**, ranging to at least 800MB/second on a 30 player server! And this is
mostly short-lived objects (Block Position).
Now, this means Minecraft **really** needs more focus on new gen to be able to even support this
allocation rate. If your new gen is too small, you will be running new gen collections 1-2+
times per second, which is awful. You will have so many pauses that TPS has risk of suffering,
and the server will not be able to keep up with the cost of GCs. Then combine the fact that
objects will now promote faster, resulting in your old gen growing faster. Given more new gen,
we are able to slow down the intervals of young gen collections, resulting in more time for
short-lived objects to die young and overall more efficient GC behavior.
4. **G1MixedGCLiveThresholdPercent:** Controls when to include regions in mixed GCs in the young
GC collection, keeping old gen tidy without doing a normal old gen GC collection. When your
memory is less than this percent, old gen won't even be included in 'mixed' collections. Mixed
are not as heavy as a full old collection, so having small incremental cleanups of old keeps
memory usage light.
Default is 65 to 85 depending on the Java version, we are setting that to 90 to ensure we reclaim garbage
in old gen as fast as possible to retain as much free regions as we can.
5. **G1ReservePercent=20:** Minecraft memory allocation rate in up-to-date versions is really insane. We
run the risk of a dreaded "to-space exhaustion" not having enough memory free to move data
around. This ensures more memory is waiting to be used for this operation. Default is 10, so we
are giving another 10 to it.
6. **MaxTenuringThreshold=1:** Minecraft has a really high allocation rate of memory. Of that
memory, most is reclaimed in the eden generation. However, transient data will overflow into
survivor. Initially played with completely removing survivor and had decent results, but does
result in transient data making its way to old which is not good. Max Tenuring 1 ensures that we
do not promote transient data to old generation, but anything that survives 2 passes of GC is
just going to be assumed as longer-lived.
Doing this greatly reduces pause times in young collections as copying data up to 15 times in
survivor space for a tenured object really takes a lot of time for actually old memory. Ideally
the GC engine would track average age for objects instead and tenure out data faster, but that
is not how it works.
Considering average GC rate is 10s to the upwards of minutes per young collection, this does not
result in any 'garbage' being promoted, and just delays longer lived memory to be collected in
mixed GCs.
7. **SurvivorRatio=32:** Because we drastically reduced MaxTenuringThreshold, we will be reducing
use of survivor space drastically. This frees up more regions to be used by eden instead.
8. **AlwaysPreTouch:** AlwaysPreTouch gets the memory setup and reserved at process start ensuring
it is contiguous, improving the efficiency of it more. This improves the operating systems
memory access speed. Mandatory to use Transparent Huge Pages
9. **+DisableExplicitGC:** Many plugins think they know how to control memory, and try to invoke
garbage collection. Plugins that do this trigger a full garbage collection, triggering a massive
lag spike. This flag disables plugins from trying to do this, protecting you from their bad
code.
10. **MaxGCPauseMillis=200:** This setting controls how much memory is used in between the minimum
and maximum ranges specified for your new generation. This is a "goal" for how long you want
your server to pause for collections. 200 is aiming for at most loss of 4 ticks. This will
result in a short TPS drop, however the server can make up for this drop instantly, meaning it
will have no meaningful impact on your TPS. 200ms is lower than players can recognize. In
testing, having this value constrained to an even lower number results in G1 not recollecting
memory fast enough and potentially running out of old gen triggering a full collection. Just
because this number is 200 does not mean every collection will be 200. It means it can use up to
200 if it really needs it, and we need to let it do its job when there is memory to collect.
11. **+ParallelRefProcEnabled:** Optimizes the GC process to use multiple threads for weak reference
checking. Not sure why this isn't default...
12. **G1RSetUpdatingPauseTimePercent=5:** Default is 10% of time spent during pause updating RSets,
reduce this to 5% to make more of it concurrent to reduce pause durations.
13. **G1MixedGCCountTarget=4:** Default is 8. Because we are aiming to collect slower, with less old
gen usage, try to reclaim old gen memory faster to avoid running out of old.
14. **G1HeapRegionSize=8M+:** Default is auto calculated. Super important for Minecraft, especially
1.15, as with low memory situations, the default calculation will in most times be too low. Any
memory allocation half of this size (4MB) will be treated as "Humongous" and promote straight to
old generation and is harder to free. If you allow Java to use the default, you will be
destroyed with a significant chunk of your memory getting treated as Humongous.
15. **+PerfDisableSharedMem:** Causes GC to write to file system which can cause major latency if
disk IO is high - see https://www.evanjones.ca/jvm-mmap-pause.html
### Transparent huge pages
Controversial feature but may be usable if you can not configure your host for real HugeTLBFS. Try
adding `-XX:+UseTransparentHugePages` but it's extremely important you also have `AlwaysPreTouch` set.
Otherwise, THP will likely hurt you. We have not measured how THP works for Minecraft or its impact with
`AlwaysPreTouch`, so this section is for the advanced users who want to experiment.
---
# Configuring Anti-Xray
Paper ships an obfuscation-based Anti-Xray system by default. Learn how to configure it here.
> Originally written and maintained by [stonar96](https://github.com/stonar96).
Paper includes an obfuscation-based Anti-Xray with three modes, configurable on a per world basis.
:::note[Per World Configuration]
If you aren't already familiar with per world configuration, please take a moment to familiarize
yourself with the [Configuration Guide](/paper/reference/configuration).
:::
This guide is a step-by-step walk-through for configuring Anti-Xray. For reference documentation,
refer to the Anti-Xray section of the
[Per-World Configuration Reference](/paper/reference/world-configuration#anticheat_anti_xray).
Anti-Xray has three different modes. `engine-mode: 1` replaces specified blocks (`hidden-blocks`) with
other "fake" blocks, `stone` (`deepslate` at y < 0), `netherrack`, or `end_stone` based on the
dimension. In contrast, `engine-mode: 2` will replace both `hidden-blocks` and `replacement-blocks`
with randomly generated `hidden-blocks`. `engine-mode: 3` works similarly to `engine-mode: 2`, but instead of
randomizing every block, it randomizes the block for each layer of a chunk.
The following images[^1] show how each mode will look for a player using Xray with the recommended
configuration in both the overworld and nether.
[^1]:
Image design by `Oberfail`, initially posted in the
[PaperMC Discord](https://discord.gg/papermc).
{/*
Seed: -7943468717341609647
# Overworld:
/tp @p -581.976 67.85076 -4924.106 47 36
# Nether:
/tp @p 789.437 117.38012 -319.064 -137.4 28
*/}


Especially on the client side, `engine-mode: 1` is much less computationally intensive, while
`engine-mode: 2` may better prevent Xray. With `engine-mode: 1`, only ores that are entirely covered
by solid blocks will be hidden. Ores exposed to air in caves or water from a lake will not be
hidden. With `engine-mode: 2`, fake ores obstruct the view of real blocks. If `air` is added to
`hidden-blocks`, `engine-mode: 2` will effectively hide all ores, even those exposed to air. `engine-mode: 3` can reduce network load when joining by a factor of ~2 and helps with chunk packet compression.
:::caution[Anti-Xray Bypasses]
**Range Extension**: While Anti-Xray alone will prevent the majority of users from Xraying on your
server, it is not by any means infallible. Because of how Anti-Xray is (and has to be) implemented,
it is possible to, on a default server, extend the range of real ores you can see by a not
insignificant amount. This can be mitigated by any competent anti-cheat plugin; however, this is not
included out of the box.
**Seed Reversing**: Another attack vector is the deterministic nature of Minecraft's world
generation. If the client is able to obtain the world seed, it is able to know the real location of
every generated ore, completely bypassing Anti-Xray. This can be partially worked around by making
it harder for the client to reverse the world seed with the
[`feature-seeds` configuration](/paper/reference/world-configuration#feature_seeds), in conjunction
with the structure seed options in `spigot.yml`. Note that this is not a complete solution, and it
may still be possible for a client to obtain the server's world seed. Using a different seed for
each world may also be beneficial.
**Ores Exposed to Air**: In `engine-mode: 1`, `engine-mode: 2` and `engine-mode: 3`, it is possible for a client
to view ores that are exposed to air. This can be mitigated in `engine-mode: 2` and `engine-mode: 3` by adding `air` to
the `hidden-blocks` list. However, doing this may cause client performance issues (FPS drops) for
some players.
:::
## Recommended configuration
The recommended configuration for `engine-mode: 1`, `engine-mode: 2` and `engine-mode: 3` is as follows:
:::tip[Spacing]
YAML cares about whitespace! The example configuration below is already formatted correctly. Ensure
formatting and indentation remains unchanged by using the "copy" button in the top right of each
example. Especially ensure that no tabulators are accidentally inserted. Check your editor's options
for using spaces instead of tabulators for indentation. If your configuration file already contains
other important changes, it is recommended to make a backup before editing it.
:::
### `engine-mode: 1`
Default World Configuration
Replace the existing `anticheat.anti-xray` block in `paper-world-defaults.yml` with the following:
```yaml title="paper-world-defaults.yml"
anticheat:
anti-xray:
enabled: true
engine-mode: 1
hidden-blocks:
# There's no chance to hide dungeon chests as they are entirely surrounded by air, but buried treasures will be hidden.
- chest
- coal_ore
- deepslate_coal_ore
- copper_ore
- deepslate_copper_ore
- raw_copper_block
- diamond_ore
- deepslate_diamond_ore
- emerald_ore
- deepslate_emerald_ore
- gold_ore
- deepslate_gold_ore
- iron_ore
- deepslate_iron_ore
- raw_iron_block
- lapis_ore
- deepslate_lapis_ore
- redstone_ore
- deepslate_redstone_ore
lava-obscures: false
# As of 1.18 some ores are generated much higher.
# Please adjust the max-block-height setting at your own discretion.
# https://minecraft.wiki/w/Ore might be helpful.
max-block-height: 64
# The replacement-blocks list is not used in engine-mode: 1. Changing this will have no effect.
replacement-blocks: []
update-radius: 2
use-permission: false
```
Nether Configuration
Copy and paste into your `paper-world.yml` within your nether world folder. See the
[Configuration Guide](/paper/reference/configuration) for more information.
```yml title="world/dimensions/minecraft/the_nether/paper-world.yml"
anticheat:
anti-xray:
enabled: true
engine-mode: 1
hidden-blocks:
- ancient_debris
- nether_gold_ore
- nether_quartz_ore
lava-obscures: false
max-block-height: 128
# The replacement-blocks list is not used in engine-mode: 1. Changing this will have no effect.
replacement-blocks: []
update-radius: 2
use-permission: false
```
End Configuration
Copy and paste into your `paper-world.yml` within your end world folder. See the
[Configuration Guide](/paper/reference/configuration) for more information.
```yml title="world/dimensions/minecraft/the_end/paper-world.yml"
anticheat:
anti-xray:
enabled: false
```
### `engine-mode: 2`
Default World Configuration
Replace the existing `anticheat.anti-xray` block in `paper-world-defaults.yml` with the following:
```yaml title="paper-world-defaults.yml"
anticheat:
anti-xray:
enabled: true
engine-mode: 2
hidden-blocks:
# You can add air here such that many holes are generated.
# This works well against cave finders but may cause client FPS drops for all players.
- air
- copper_ore
- deepslate_copper_ore
- raw_copper_block
- diamond_ore
- deepslate_diamond_ore
- gold_ore
- deepslate_gold_ore
- iron_ore
- deepslate_iron_ore
- raw_iron_block
- lapis_ore
- deepslate_lapis_ore
- redstone_ore
- deepslate_redstone_ore
lava-obscures: false
# As of 1.18 some ores are generated much higher.
# Please adjust the max-block-height setting at your own discretion.
# https://minecraft.wiki/w/Ore might be helpful.
max-block-height: 64
replacement-blocks:
# Chest is a tile entity and can't be added to hidden-blocks in engine-mode: 2.
# But adding chest here will hide buried treasures, if max-block-height is increased.
- chest
- amethyst_block
- andesite
- budding_amethyst
- calcite
- coal_ore
- deepslate_coal_ore
- deepslate
- diorite
- dirt
- emerald_ore
- deepslate_emerald_ore
- granite
- gravel
- oak_planks
- smooth_basalt
- stone
- tuff
update-radius: 2
use-permission: false
```
Nether Configuration
Copy and paste into your `paper-world.yml` within your nether world folder. See the
[Configuration Guide](/paper/reference/configuration) for more information.
```yml title="world/dimensions/minecraft/the_nether/paper-world.yml"
anticheat:
anti-xray:
enabled: true
engine-mode: 2
hidden-blocks:
# See note about air and possible client performance issues above.
- air
- ancient_debris
- bone_block
- glowstone
- magma_block
- nether_bricks
- nether_gold_ore
- nether_quartz_ore
- polished_blackstone_bricks
lava-obscures: false
max-block-height: 128
replacement-blocks:
- basalt
- blackstone
- gravel
- netherrack
- soul_sand
- soul_soil
update-radius: 2
use-permission: false
```
End Configuration
Copy and paste into your `paper-world.yml` within your end world folder. See the
[Configuration Guide](/paper/reference/configuration) for more information.
```yml title="world/dimensions/minecraft/the_end/paper-world.yml"
anticheat:
anti-xray:
enabled: false
```
### `engine-mode: 3`
Default World Configuration
Replace the existing `anticheat.anti-xray` block in `paper-world-defaults.yml` with the following:
```yaml title="paper-world-defaults.yml"
anticheat:
anti-xray:
enabled: true
engine-mode: 3
hidden-blocks:
# You can add air here such that many holes are generated.
# This works well against cave finders but may cause client FPS drops for all players.
- air
- copper_ore
- deepslate_copper_ore
- raw_copper_block
- diamond_ore
- deepslate_diamond_ore
- gold_ore
- deepslate_gold_ore
- iron_ore
- deepslate_iron_ore
- raw_iron_block
- lapis_ore
- deepslate_lapis_ore
- redstone_ore
- deepslate_redstone_ore
lava-obscures: false
# As of 1.18 some ores are generated much higher.
# Please adjust the max-block-height setting at your own discretion.
# https://minecraft.wiki/w/Ore might be helpful.
max-block-height: 64
replacement-blocks:
# Chest is a tile entity and can't be added to hidden-blocks in engine-mode: 2.
# But adding chest here will hide buried treasures, if max-block-height is increased.
- chest
- amethyst_block
- andesite
- budding_amethyst
- calcite
- coal_ore
- deepslate_coal_ore
- deepslate
- diorite
- dirt
- emerald_ore
- deepslate_emerald_ore
- granite
- gravel
- oak_planks
- smooth_basalt
- stone
- tuff
update-radius: 2
use-permission: false
```
Nether Configuration
Copy and paste into your `paper-world.yml` within your nether world folder. See the
[Configuration Guide](/paper/reference/configuration) for more information.
```yml title="world/dimensions/minecraft/the_nether/paper-world.yml"
anticheat:
anti-xray:
enabled: true
engine-mode: 3
hidden-blocks:
# See note about air and possible client performance issues above.
- air
- ancient_debris
- bone_block
- glowstone
- magma_block
- nether_bricks
- nether_gold_ore
- nether_quartz_ore
- polished_blackstone_bricks
lava-obscures: false
max-block-height: 128
replacement-blocks:
- basalt
- blackstone
- gravel
- netherrack
- soul_sand
- soul_soil
update-radius: 2
use-permission: false
```
End Configuration
Copy and paste into your `paper-world.yml` within your end world folder. See the
[Configuration Guide](/paper/reference/configuration) for more information.
```yml title="world/dimensions/minecraft/the_end/paper-world.yml"
anticheat:
anti-xray:
enabled: false
```
## FAQ, common pitfalls and support
I can still see (some) ores / use X-ray
As described above, there are several reasons why you might still see (some) ores even though you
have enabled Anti-Xray:
* The ores are above the configured `max-block-height` value.
* Anti-Xray cannot hide ores exposed to air or other transparent blocks (in caves for example). In
principle this is also the case for `engine-mode: 2` and `engine-mode: 3`, however, usually the fake ores obstruct the
view of real blocks. Hiding those exposed ores too requires additional plugins.
* The `use-permission` option is enabled and you have the Anti-Xray bypass permission (`paper.antixray.bypass`) or you have
operator status.
* The block type is missing in the configured block lists. This can be the result of using an
outdated configuration file.
I have added fake blocks but X-ray doesn't show them
If you use `engine-mode: 2` or `engine-mode: 3` and you have added fake blocks to the `hidden-blocks` list but you can't
see them in-game using X-ray, this can have the following reasons:
* The added block types are tile entities. Anti-Xray can hide (replace) tile entities (such as
chests), provided that they are not exposed to air or other transparent blocks. However, Anti-Xray
can't place tile entities as fake blocks into the chunk.
* The block is disabled in your client's X-ray mod or not shown by your X-ray resource pack.
It doesn't work below y = 0 or in certain other places.
* Your configuration file is probably outdated and missing important blocks in the
`replacement-blocks` list, such as `deepslate` or biome-specific blocks, such as `basalt`. You
might also want to check if the `hidden-blocks` list includes all important ores and their
`deepslate` variants.
* If it doesn't work above a certain y-level, check your `max-block-height` setting.
It still doesn't work, further troubleshooting
* Make sure to always restart your server after making changes to the Anti-Xray configuration.
Changes won't be applied automatically.
* Do not use the `/reload` command. To apply Anti-Xray configuration changes a restart is required.
* After restarting the server, verify that the configuration is applied correctly by inspecting the
config sections with timings or spark.
How and where do I ask for support if it still doesn't work?
If the above bullet points don't solve your problem or if you have further questions about
Anti-Xray, please don't hesitate to ask us on the [PaperMC Discord](https://discord.gg/papermc)
using the #paper-help channel. Please try to provide as much detail as possible about your problem.
"It doesn't work" isn't very helpful when asking for support. Describe what you want to achieve,
what you have tried, what you expect and what you observe. Ideally include a timings or spark link
and a picture what you observe in-game.
---
# Basic troubleshooting
This guide will help you diagnose your server's problem before reporting it to PaperMC or the plugin's author.
This guide will help you diagnose your server's problem before reporting it to PaperMC or the plugin's author.
:::caution[Stop Your Server And Take A Backup]
Before following this guide, stop your server first. Modifying server files while it is still running will corrupt them. Only a full server shutdown can prevent this.
Also, if you don't follow this guide carefully or make a mistake while following it, you might corrupt your server. It is highly advised to back up your server before following this guide. Archiving your server folder as a .zip is good enough, or if you prefer, use backup software such as [borg](https://www.borgbackup.org/) or [kopia](https://kopia.io/). It would be ideal to create a test server by copying your production server's file, but that's not always possible.
:::
If your server encounters a problem, it will either print an error message on the server console, create a crash report and close itself, or do both.
If your server crashes, the crash report will be saved in the crash-report directory. If your server didn't crash, those error messages will be stored in the log directory along with other messages.
Note that the logs older than the latest will be compressed and not stored as plain text files.
The first thing you have to do is diagnose those messages.
Almost every problem you encounter will print error message lines, which are called a "stack trace", on the server console. Examining the stack trace will help you find out what is causing problems on your server.
The stack trace starts with the error message, exception type, and exception message.
Both error messages and exception messages were put there by the developer of either your plugin or Paper. These messages tell you what problem your server experienced. An exception type like `java.lang.RuntimeException` tells you the type of the exception. This will help the developer (and you) understand the type of problem. A good starting point is to search the exception type and message in the [Paper Discord](https://discord.gg/papermc).
Many lines beginning with `at` may appear beneath the exception message. These are the body of the stack trace. These lines tell you where the problem starts. The top line of the body of the stack trace will tell you exactly where the problem occurred and, if possible, display where it came from.
Issues are often plugin-induced, and are the first possible thing you should check.
# Common issues
## Plugin-induced issues
If you find any plugin's name in a stack trace in your logs, head to [Check Plugin Updates](#check-plugin-updates) and read from there. In most cases, the plugin, whose name is found on the stack trace, is causing the problem.
You can disable all of your plugins by renaming the plugins directory to something else, such as plugins-disabled, or by archiving the plugins directory and deleting it.
After that, try to run your server.
If the problem is resolved after removing the plugins, you know that it was a plugin that caused the issue.
### Binary search
In case you've determined a plugin is causing issues but cannot narrow it down, try a binary search.
1. Split your plugins into two groups. The size of the two groups can be different, but it is ideal if the difference is minimal. Make sure that plugins that depend on each other are grouped together.
2. Disable one of the two groups of plugins. You can disable them by changing their extension from .jar to something else, such as .jar-disabled, or move them outside the plugins directory and into a temporary directory.
3. Run your server and check if the problem still exists. If the problem is resolved, the plugin that caused the issue is one of the disabled plugins. If the problem is not resolved, the plugin that is causing the issue is one of the active plugins.
4. Repeat from the start with the suspect plugin group.
Repeat the steps above with groups that have the plugin that is causing the issue.
:::caution[Library Plugin Dependencies]
Some plugins that you install are not a typical plugin, but a library. These are installed like plugins, however tend to offer few user-facing features and are relied upon by other plugins for their functionality. If you disable a library, plugins that depend on it will not work properly. Common examples of these libraries are ProtocolLib, Vault providers, permission plugins, etc.
:::
### Check plugin updates
There is a chance that your problem is already fixed in the latest release or latest build of the plugin.
Head to your plugin's official download page and check if you are using the latest build or the latest release of the plugin. If not, update it to the latest version and try to run your server again to see if the problem is resolved.
### Update library plugins
Many plugins use library plugins like ProtocolLib, and you have to download them and put them in the plugins directory.
If you don't update them to the latest version or latest build, you might experience problems related to plugins that use the library plugin.
Some library plugins tell their users to use their latest development build for support of the latest Minecraft version. You should look carefully at the requirements of your plugin.
### Check documentation
If you misconfigured your plugin or your server, it can also cause problems on your server. Many plugins provide their own documentation about how to set them up properly. Read those documents carefully and check if there is something wrong with the plugin's configuration.
If your problem is related to a plugin you use, and you still don't know how to solve it, you can try to reach out to the plugin's author. Many plugins have a way to contact their author, like a GitHub issue tracker, Discord support guild, Gitter, IRC, etc.
Below, we list other issues that may happen when running a server.
## Server does not start
When this happens, always check your `latest.log` file in your `logs` folder, you may find your issue listed here. If logs are not generating, check your startup script, as described below:
### Checking your startup script
The recommended way to start a server is via a startup script, that you can generate [here](/misc/tools/start-script-gen). Don't double click the .jar!
If you're on Windows and your terminal disappears quickly after you run, make sure there's a line at the end of the file containing just `pause`.
In case you get an error similar to `Error: Unable to access jarfile server.jar`, make sure that the .jar name in your startup script is the same as the file you downloaded. Note that Windows, by default, hides extensions, so you may need re-enable that in the Folder and Search Options in the file explorer to see the correct name of the file, extension included.
### Failed to bind to port
This may happen in two cases:
1. A server is already running, check your task manager app for Java processes.
2. `server-ip`, in `server.properties`, is configured incorrectly. Note that this option is not a placeholder for your external IP, it controls which network interfaces your server will bind to. Most of the time, it should be left empty.
### Attempted to load chunk saved with newer version
{/* spellchecker:off */}
```
java.lang.RuntimeException: Server attempted to load chunk saved with newer version of minecraft! 3955 > 3465
[18:23:38 WARN]: at net.minecraft.world.level.chunk.storage.ChunkRegionLoader.loadChunk(ChunkRegionLoader.java:149)
[18:23:38 WARN]: at io.papermc.paper.chunk.system.scheduling.ChunkLoadTask$ChunkDataLoadTask.runOffMain(ChunkLoadTask.java:338)
[18:23:38 WARN]: at io.papermc.paper.chunk.system.scheduling.GenericDataLoadTask$ProcessOffMainTask.run(GenericDataLoadTask.java:307)
[18:23:38 WARN]: at ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue$PrioritisedTask.executeInternal(PrioritisedThreadedTaskQueue.java:351)
[18:23:38 WARN]: at ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue.executeTask(PrioritisedThreadedTaskQueue.java:118)
[18:23:38 WARN]: at ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool$PrioritisedThread.pollTasks(PrioritisedThreadPool.java:274)
[18:23:38 WARN]: at ca.spottedleaf.concurrentutil.executor.standard.PrioritisedQueueExecutorThread.run(PrioritisedQueueExecutorThread.java:50)
```
{/* spellchecker:on */}
That error means that your world was created or opened in a server version that's newer than one you're currently running. Downgrading your world is not supported, so make sure to use the latest supported version of Paper. Even if you haven't joined the server, by loading your world in a newer version, it is upgraded automatically.
:::danger[Forcing the server to try to load a newer world]
The server will start if you use the `-DPaper.ignoreWorldDataVersion=true` flag. However, this is **highly not recommended, completely unsupported and may permanently corrupt your world**. If you're going to attempt this, take a backup.
:::
### Circular plugin loading
```
[15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] Circular plugin loading detected!
[15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] Circular load order:
[15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] Plugin1 -> Plugin2 -> Plugin3 -> Plugin4 -> Plugin1
[15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] Please report this to the plugin authors of the first plugin of each loop or join the PaperMC Discord server for further help.
[15:01:04] [Server thread/ERROR]: [SimpleProviderStorage] If you would like to still load these plugins, acknowledging that there may be unexpected plugin loading issues, run the server with -Dpaper.useLegacyPluginLoading=true
```
That means your plugins are configured in a way such that they want to start before (or after) each other, which is impossible -- one has to go first. Plugins usually have reasons to want to start before each other, so when such a conflict happens, rather than picking randomly and risking issues, the server warns you about the issue and shuts down.
There's often a problematic plugin involved, and to solve this, it's preferable that you report the issue to its authors. Removing it should also fix the issue. As a last resort, you can use the `-Dpaper.useLegacyPluginLoading=true` startup flag, but it may cause hard to debug issues.
### Outdated version of Java
```
Exception in thread "ServerMain" java.lang.UnsupportedClassVersionError: org/bukkit/craftbukkit/Main has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:524)
at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:427)
at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:421)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:420)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:467)
at io.papermc.paperclip.Paperclip.lambda$main$0(Paperclip.java:38)
at java.base/java.lang.Thread.run(Thread.java:842)
```
Your version of Java is outdated, check [our guide on updating it](/misc/java-install). To avoid possibly having to do more tweaks, uninstall your current version of Java, if any.
If you do have the correct version installed, your operating system may be not picking it up. Make sure you've closed and opened your terminal after installing it, and that Java is present in your `PATH` environment variable.
## Server crashes or exits unexpectedly
:::caution[Update!]
Always keep your server up to date (and take a backup before updating). Older versions are known to have on-demand crashes that can be triggered by players at any time.
:::
### Unexpected graceful shutdown
If your server shuts down normally as if you typed `/stop` or pressed a stop button in your panel, enable `debug` in `server.properties`. The next time the server shuts down, you will get a stack trace that will help you debug.
### Watchdog dump ("DO NOT REPORT THIS TO PAPER")
```
[02:04:00] [Paper Watchdog Thread/ERROR]: --- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - 1.21.3-66-afb5b13 (MC: 1.21.3) ---
[02:04:00] [Paper Watchdog Thread/ERROR]: The server has not responded for 10 seconds! Creating thread dump
[02:04:00] [Paper Watchdog Thread/ERROR]: ------------------------------
[02:04:00] [Paper Watchdog Thread/ERROR]: Server thread dump (Look for plugins here before reporting to Paper!):
[02:04:00] [Paper Watchdog Thread/ERROR]: ------------------------------
[02:04:00] [Paper Watchdog Thread/ERROR]: Current Thread: Server thread
[02:04:00] [Paper Watchdog Thread/ERROR]: PID: 129 | Suspended: false | Native: true | State: RUNNABLE
[02:04:00] [Paper Watchdog Thread/ERROR]: Stack:
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.UnixFileDispatcherImpl.write0(Native Method)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.UnixFileDispatcherImpl.write(UnixFileDispatcherImpl.java:65)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:137)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.IOUtil.write(IOUtil.java:102)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.IOUtil.write(IOUtil.java:72)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:300)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.ChannelOutputStream.writeFully(ChannelOutputStream.java:68)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/sun.nio.ch.ChannelOutputStream.write(ChannelOutputStream.java:105)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:125)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/java.io.BufferedOutputStream.implFlush(BufferedOutputStream.java:252)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/java.io.BufferedOutputStream.flush(BufferedOutputStream.java:240)
[02:04:00] [Paper Watchdog Thread/ERROR]: java.base@21.0.5/java.io.FilterOutputStream.close(FilterOutputStream.java:184)
```
That message shows up when your server is taking very long (10+ seconds) to finish the current tick -- it's not a bug or crash, it's simply warning you of severe lag, that can eventually lead to a crash.
A good rule of thumb is checking the first lines of the stack trace, as that shows where the main thread was stuck at the time it was printed. Many times, that points to the root cause of the issue.
However, sometimes the issue may not be obvious or appear in the stack trace. If possible, analyze a [spark report](#spark-report) for more details, and, if you're in doubt about how to proceed, feel free to visit the [Paper Discord](https://discord.gg/papermc) for help.
### Crash without logs
If you have access to a Linux shell, run `dmesg -T | grep -i killed`. That should show how your server process was killed.
A common cause (but not the only one), if it was killed due to OOM, is that your server panel is configured with a memory limit that's too close to your `-Xmx`. Either reduce `-Xmx` (by 1-2GB is a good initial rule of thumb) or increase/disable the memory limits in your panel.
If you're using a hosting company that only provides you with a panel, you likely won't have the tools to get to the bottom of the problem. You should make a ticket with your host in this case.
## Performance and gameplay issues
Unfortunately, Paper can't replicate Vanilla behavior 100%, but it is a goal (except when it comes to exploits). If you're still experiencing a bug that cannot be reproduced in vanilla **multiplayer**, please apply a [Vanilla-like configuration](/paper/vanilla) and check if there's already an open issue in our GitHub. If not, feel free to create one.
### Strange entity/farm/redstone/spawning behavior
If you copied values of a pre-made configuration or optimization guide, now is a good time to revert the changes. Keep a copy of your current configs if you prefer, and delete the originals so they can re-generate to default values.
In case you're still experiencing such issues with default configurations, try our [Vanilla-like configurations](/paper/vanilla) but do note that this comes at a performance cost.
Also, keep in mind that singleplayer does not behave the same as multiplayer, both when it comes to spawning and certain entity behavior. For example, mobs despawn if they're over 128 blocks away from a player, and this becomes more apparent in multiplayer, especially if you're making farms where monsters go to another dimension via a nether portal. If there are players in the target dimension and they're all very far from the portal, the mob will instantly despawn -- this is intended Vanilla behavior.
### Dupes not working
Paper has some [unsupported settings](/paper/reference/global-configuration#unsupported_settings) that allow certain dupes. However, a few of them cannot be re-introduced because that would break other aspects of the server. Paper also will not re-add dupes that no longer exist in the game.
## Performance issues
### Spark report
Paper has the [spark](https://spark.lucko.me/) profiler built-in, in order to diagnose the root cause of performance issues.
For example, you can generate a report for 300 seconds by running `/spark profiler start --timeout 300`
If you want to diagnose lag spikes that last more than, for example, 100 ms, you can run `/spark profiler start --only-ticks-over 100 --timeout 300`
Look into the [spark docs](https://spark.lucko.me/docs) for a more in-depth guide on how to use it and read reports. If you're still unsure about what's the cause of your problem, you can send us the link via the [Paper Discord](https://discord.gg/papermc).
### High RAM usage
Unless you're experiencing out of memory crashes or bad garbage collection (GC) times, high memory usage is expected.
Java programs store objects in memory, in an area where we call the heap. The heap grows over time, you can set its initial value with `-Xms` and the maximum value with `-Xmx`.
It's normal that the Java process will use the RAM it's given, sometimes using a little more than `-Xmx`. This number won't go down over time, as the garbage collector (GC) rarely returns RAM to the operating system in servers. This is by itself not a problem, in fact it's beneficial: by having more heap memory to work with, GC will have to worry less about disposing of garbage, as that takes valuable processing time. This is not a memory leak and will not cause out of memory crashes, which are commonly caused by not leaving enough RAM for your OS or improperly configured memory limits in a container.
There are several different memory metrics that can be measured. For example, imagine a server in a 16GB container, running with `-Xms1G -Xmx14G`. In this case:
* **Container memory limit:** 16GB
* **Maximum heap:** 14GB
* **Current heap size:** Starts at 1GB and expands quickly. Will be between 1-14GB at any given point
* **Current heap usage:** will be smaller than the number above, and will grow and shrink in a sawtooth pattern under normal conditions
Keep in mind that different tools choose different metrics out of these to display, so the usage meter in your panel, at a glance, might not look like what a plugin will display.
### Low CPU usage
Paper is able to make use of multiple cores, but this does not necessarily mean that you will have several cores at near 100% usage. A major source of load in the server comes from the tick loop, which uses a single thread. Thus, at a certain point, more cores will not give you a performance benefit, so it's advisable to go for a CPU with high single-threaded performance and allocate a sufficient amount of threads to your server (at least 4). In servers with a high core count, this situation can translate to a low CPU usage relative to the total amount of cores.
However, if your server (especially if large) is really adamant on using only a single core, check your panel's CPU allocation setting. In certain panels, a number like `4` doesn't mean it'll use 4 cores, but instead that it will use the one core with ID 4.
---
# Contributing
import PageCards from "/src/components/PageCards.astro";
Welcome to the Paper contributing guide! This guide includes information and tutorials for developers
wishing to contribute to the Paper project.
---
# Events
A guide on how to add new events to Paper.
There are several requirements for events in the Paper API.
:::note
Note that while not all existing events may follow these
guidelines, all new and modified events should adhere to them.
:::
All new events should go in the package (sub-package of) `io.papermc.paper.event`.
### Constructors
All new constructors added should be annotated with
[`@ApiStatus.Internal`](https://javadoc.io/doc/org.jetbrains/annotations/latest/org/jetbrains/annotations/ApiStatus.Internal.html)
to signify that they are not considered API and can change at any time without warning.
Constructors that are being replaced, if they aren't being removed, should be marked with
[`@Deprecated`](jd:java:java.lang.Deprecated) and [`@DoNotUse`](jd:paper:io.papermc.paper.annotation.DoNotUse).
### Mutability
Certain API types are "mutable" which can lead to unexpected behavior within events. Mutable types like
[`Location`](jd:paper:org.bukkit.Location) and [`Vector`](jd:paper:org.bukkit.util.Vector)
should therefore be cloned when returned from a "getter" in an event.
### `HandlerList`
For an event class or any subclass of it to be listened to, a [`HandlerList`](jd:paper:org.bukkit.event.HandlerList)
field must be present with an instance and static method to retrieve it.
See the docs for [`Event`](jd:paper:org.bukkit.event.Event) for specifics.
This field should be static and final and named `HANDLER_LIST`.
Also consider not putting a `HandlerList` on every event, just a "common parent" event so that a plugin can listen to the
parent event and capture any child events but also listen to the child event separately.
### Miscellaneous
* New parameters or method returns of type [`ItemStack`](jd:paper:org.bukkit.inventory.ItemStack)
should not be [`@Nullable`](https://javadoc.io/doc/org.jspecify/jspecify/latest/org/jspecify/annotations/Nullable.html)
in most case and instead accept an empty itemStack.
---
# Development
import PageCards from "/src/components/PageCards.astro";
Welcome to the Paper development guide! This guide includes information and tutorials for developers
on how to create and expand on Paper plugins.
#### Getting started
#### API
#### Miscellaneous
---
# API
import PageCards from "/src/components/PageCards.astro";
Welcome to the Paper API guide!
This guide includes information for developers about how to use specific parts of the Paper API.
---
# Command API
import PageCards from "/src/components/PageCards.astro";
#### Basics
#### Arguments
#### Miscellaneous
---
# Arguments
import PageCards from "/src/components/PageCards.astro";
---
# Basics
import PageCards from "/src/components/PageCards.astro";
---
# Miscellaneous
import PageCards from "/src/components/PageCards.astro";
---
# Component API
import PageCards from "/src/components/PageCards.astro";
---
# Entity API
import PageCards from "/src/components/PageCards.astro";
---
# Event API
import PageCards from "/src/components/PageCards.astro";
---
# Inventories
import PageCards from "/src/components/PageCards.astro";
---
# Lifecycle API
import PageCards from "/src/components/PageCards.astro";
---
# Chat events
An outline on AsyncChatEvent and how to handle it.
The chat event has evolved a few times over the years.
This guide will explain how to properly use the new [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent)
and its [](jd:paper:io.papermc.paper.chat.ChatRenderer).
The [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent)
is an improved version of the old [](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent)
that allows you to render chat messages individually for each player.
:::note[`AsyncChatEvent` vs `ChatEvent`]
The key difference between [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent)
and [](jd:paper:io.papermc.paper.event.player.ChatEvent) is that
[](jd:paper:io.papermc.paper.event.player.AsyncChatEvent) is fired asynchronously.
This means that it does not block the main thread and sends the chat message when the listener has completed.
Be aware that using the Bukkit API in an asynchronous context (i.e. the event handler) is unsafe and exceptions may be thrown.
If you need to use the Bukkit API, you can use [](jd:paper:io.papermc.paper.event.player.ChatEvent).
However, we recommend using [`BukkitScheduler`](/paper/dev/scheduler).
:::
## Understanding the renderer
Before we can start using the new chat event, we need to understand how the new renderer works.
The renderer is Paper's way of allowing plugins to modify the chat message before it is sent to the player.
This is done by using the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface with its
[](jd:paper:io.papermc.paper.chat.ChatRenderer#render(org.bukkit.entity.Player,net.kyori.adventure.text.Component,net.kyori.adventure.text.Component,net.kyori.adventure.audience.Audience))
method. Previously, this was done by using the [](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent)
with its [](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent#setFormat(java.lang.String)) method.
```java title="ChatRenderer#render"
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
// ...
}
```
- The [`render`](jd:paper:io.papermc.paper.chat.ChatRenderer#render(org.bukkit.entity.Player,net.kyori.adventure.text.Component,net.kyori.adventure.text.Component,net.kyori.adventure.audience.Audience)) method is called when a chat message is sent to the player.
- The `source` parameter is the player that sent the message.
- The `sourceDisplayName` parameter is the display name of the player that sent the message.
- The `message` parameter is the message that was sent.
- The `viewer` parameter is the player that is receiving the message.
:::tip[`ChatRenderer.ViewerUnaware`]
If your renderer does not need to know about the viewer, you can use the
[](jd:paper:io.papermc.paper.chat.ChatRenderer$ViewerUnaware)
interface instead of the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface.
This will benefit performance as the message will only be rendered once instead of each individual player.
:::
## Using the renderer
There are two ways to use the renderer.
1. Implementing the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface in a class.
2. Using a lambda expression.
Depending on the complexity of your renderer, you may want to use one or the other.
### Implementing the `ChatRenderer` interface
The first way of using the renderer is by implementing the [](jd:paper:io.papermc.paper.chat.ChatRenderer)
interface in a class. In this example, we will be using our `ChatListener` class.
Next, we need to tell the event to use the renderer by using the
[](jd:paper:io.papermc.paper.event.player.AbstractChatEvent#renderer()) method.
```java title="ChatListener.java"
public class ChatListener implements Listener, ChatRenderer { // Implement the ChatRenderer and Listener interface
// Listen for the AsyncChatEvent
@EventHandler
public void onChat(AsyncChatEvent event) {
event.renderer(this); // Tell the event to use our renderer
}
// Override the render method
@Override
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
// ...
}
}
```
:::note
If you decide to create a separate class for your renderer, it is important to know that you don't need to instantiate the class every time the event is called.
In this case, you can use [the singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) to create a single instance of the class.
:::
### Using a lambda expression
Another way of using the renderer is by using a lambda expression.
```java title="ChatListener.java"
public class ChatListener implements Listener {
@EventHandler
public void onChat(AsyncChatEvent event) {
event.renderer((source, sourceDisplayName, message, viewer) -> {
// ...
});
}
}
```
## Rendering the message
Now that we have our renderer, we can start rendering the message.
Let's say we want to render our chat to look like this:

To do this, we need to return a new [`Component`](jd:adventure:net.kyori.adventure.text.Component) that contains the message we want to send.
```java title="ChatListener.java"
public class ChatListener implements Listener, ChatRenderer {
// Listener logic
@Override
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
return sourceDisplayName
.append(Component.text(": "))
.append(message);
}
}
```
Now you can see that the message is rendered as we wanted it to be.
## Conclusion
That is all you need to know about the new chat event and its renderer.
Of course there are many more things you can do with components in general.
If you want to learn more about components, you can read the [Component Documentation](/adventure/text/).
---
# Adventure
Documentation for all arguments returning Adventure API objects.
import ComponentMp4 from "./assets/vanilla-arguments/component.mp4?url";
import KeyMp4 from "./assets/vanilla-arguments/key.mp4?url";
import NamedColorMp4 from "./assets/vanilla-arguments/namedcolor.mp4?url";
import StyleMp4 from "./assets/vanilla-arguments/style.mp4?url";
import SignedMessageMp4 from "./assets/vanilla-arguments/signedmessage.mp4?url";
import Video from "/src/components/Video.astro";
These arguments return a class from the `net.kyori` package. They are technically not native to Minecraft or Bukkit, but as Paper includes the Adventure library, they are
widely used in the Paper ecosystem.
## Component argument
:::note
This argument is very technical. Following the same format as the `/tellraw ` command for its second argument, it expects the JSON
representation of a text component, making it inappropriate for general user input.
:::
The result is returned as an Adventure component to work with.
### Example usage
```java
public static LiteralCommandNode componentArgument() {
return Commands.literal("componentargument")
.then(Commands.argument("arg", ArgumentTypes.component())
.executes(ctx -> {
final Component component = ctx.getArgument("arg", Component.class);
ctx.getSource().getSender().sendRichMessage(
"Your message: ",
Placeholder.component("input", component)
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## Key argument
The key argument allows a user to put in any artificial (namespaced) key, ensuring its validity. This returns a [`Key`](jd:adventure:net.kyori.adventure.key:net.kyori.adventure.key.Key),
which can be used at various other places in the Paper API.
### Example usage
```java
public static LiteralCommandNode keyArgument() {
return Commands.literal("key")
.then(Commands.argument("key_input", ArgumentTypes.key())
.executes(ctx -> {
final Key key = ctx.getArgument("key_input", Key.class);
ctx.getSource().getSender().sendRichMessage("You put in !",
Placeholder.unparsed("key", key.asString())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## Named color argument
This argument provides the user with the ability to select between the 16 built-in "named" text colors. This argument returns a
[`NamedTextColor`](jd:adventure:net.kyori.adventure.text.format.NamedTextColor),
which you can use for applying a color to components.
### Example usage
```java
public static LiteralCommandNode namedColorArgument() {
return Commands.literal("namedcolor")
.then(Commands.argument("color", ArgumentTypes.namedColor())
.then(Commands.argument("message", StringArgumentType.greedyString())
.executes(ctx -> {
final NamedTextColor color = ctx.getArgument("color", NamedTextColor.class);
final String msg = StringArgumentType.getString(ctx, "message");
ctx.getSource().getSender().sendMessage(
Component.text(msg).color(color)
);
return Command.SINGLE_SUCCESS;
})))
.build();
}
```
### In-game preview
## Adventure style argument
:::note
Similar to the component argument, this argument is not really appropriate for general user input, as it also follows the JSON format for displaying components. Most users
do not know how to use that format and thus its general usage is not advised.
:::
The style argument returns its value in the form of a [`Style`](jd:adventure:net.kyori.adventure.text.format.Style) object.
This can be applied to any component using `Component#style(Style)`. Whilst the JSON input allows for the `text` field, its content is completely ignored.
### Example usage
```java
public static LiteralCommandNode styleArgument() {
return Commands.literal("style")
.then(Commands.argument("style", ArgumentTypes.style())
.then(Commands.argument("message", StringArgumentType.greedyString())
.executes(ctx -> {
final Style style = ctx.getArgument("style", Style.class);
final String msg = StringArgumentType.getString(ctx, "message");
ctx.getSource().getSender().sendRichMessage("Your input: ",
Placeholder.component("input", Component.text(message).style(style))
);
return Command.SINGLE_SUCCESS;
})))
.build();
}
```
### In-game preview
## Signed message argument
The signed message argument allows a player to send an argument in the form of a **signed message** to the server. This signed message is a special type - it
allows the server to send that message, without the ability to directly modify it, to any player. The visible difference is that unsigned messages have a white bar at the left,
whilst signed messages don't.
A signed message argument returns a `SignedMessageResolver`. In order to call its `#resolve` method, you have to pass in two parameters:
* The argument name
* The `CommandContext` object
The resolved value is a `CompletableFuture`, whose [`SignedMessage`](jd:adventure:net.kyori.adventure.chat.SignedMessage)
value you can handle using `thenAccept(Consumer)`. Inside of the consumer, you can send the signed message to players or work with it in other ways.
:::caution
By default, the consumer passed into `thenAccept` is not executed on the main thread, making it unsafe to use most of Paper API within it.
If you need to use the API, you can schedule a task to be run on the next available tick. For this you can use the
[main thread executor](jd:paper:org.bukkit.scheduler.BukkitScheduler#getMainThreadExecutor(org.bukkit.plugin.Plugin)).
You can read up on that [here](/paper/dev/scheduler).
:::
:::note
A non-player sender is not capable of sending a signed message, which means that the resolved `CompletableFuture` will never be completed.
You should make sure that only players can use your argument with `.requires(ctx -> ctx.getSender() instanceof Player)` on your `SignedArgument`. You may
add a fallback greedy string argument for non-player senders if you want the argument to execute regardless of signing.
:::
### Example usage
```java title="MinecraftArguments.java"
public static LiteralCommandNode signedMessageArgument() {
return Commands.literal("signedmessage")
.then(Commands.argument("target", ArgumentTypes.player())
.then(Commands.argument("message", ArgumentTypes.signedMessage())
.executes(MinecraftArguments::executeSignedMessageCommand)))
.build();
}
private static int executeSignedMessageCommand(final CommandContext ctx) throws CommandSyntaxException {
final Player target = ctx.getArgument("target", PlayerSelectorArgumentResolver.class).resolve(ctx.getSource()).getFirst();
final SignedMessageResolver messageResolver = ctx.getArgument("message", SignedMessageResolver.class);
messageResolver.resolveSignedMessage("message", ctx).thenAccept(msg -> {
target.sendMessage(msg, ChatType.CHAT.bind(ctx.getSource().getSender().name()));
});
return Command.SINGLE_SUCCESS;
}
```
### In-game preview
---
# Entities and players
Player and Entity arguments documentation.
import EntityOppedMp4 from "./assets/vanilla-arguments/entity-opped.mp4?url";
import EntityUnoppedMp4 from "./assets/vanilla-arguments/entity-unopped.mp4?url";
import EntitiesMp4 from "./assets/vanilla-arguments/entities.mp4?url";
import PlayerMp4 from "./assets/vanilla-arguments/player.mp4?url";
import PlayersMp4 from "./assets/vanilla-arguments/players.mp4?url";
import PlayerProfilesMp4 from "./assets/vanilla-arguments/playerprofiles.mp4?url";
import Video from "/src/components/Video.astro";
The arguments described in this section relate to arguments which you can use to retrieve entities. Their main usage is the selection of command targets.
All of these have entity selectors (`@a`, `@e`, `@n`, etc.) as valid inputs, though they require the `minecraft.command.selector` permission in order to
be able to be used. The specific arguments may allow or disallow certain selectors.
Due to the permission requirement for selectors it is advised to add a `requires` statement to your command:
```java
.requires(ctx -> ctx.getSender().hasPermission("minecraft.command.selector"))
```
You can find more information about requirements [here](/paper/dev/command-api/basics/requirements).
## Entity argument
This argument, after resolving its returning `EntitySelectorArgumentResolver`, returns a list of exactly one, no more and no less, entity. It is safe
to call `List#getFirst()` to retrieve that entity. You can resolve it using [`ArgumentResolver#resolve(CommandSourceStack)`](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.ArgumentResolver#resolve(io.papermc.paper.command.brigadier.CommandSourceStack))
### Example usage
```java
public static LiteralCommandNode entityArgument() {
return Commands.literal("entityarg")
.then(Commands.argument("arg", ArgumentTypes.entity())
.executes(ctx -> {
final EntitySelectorArgumentResolver entitySelectorArgumentResolver = ctx.getArgument("arg", EntitySelectorArgumentResolver.class);
final List entities = entitySelectorArgumentResolver.resolve(ctx.getSource());
ctx.getSource().getSender().sendRichMessage("Found ",
Placeholder.component("entityname", entities.getFirst().name())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
If the executing player doesn't have the `minecraft.command.selector` permission:
If the executing player has the `minecraft.command.selector` permission:
## Entities argument
In contrast to the single entity argument, this multiple-entities argument accepts any amount of entities, with the minimum amount of entities being 1. They can, once again, be resolved using
[`ArgumentResolver#resolve(CommandSourceStack)`](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.ArgumentResolver#resolve(io.papermc.paper.command.brigadier.CommandSourceStack)),
which returns a `List`.
### Example usage
```java
public static LiteralCommandNode entitiesArgument() {
return Commands.literal("entitiesarg")
.then(Commands.argument("arg", ArgumentTypes.entities())
.executes(ctx -> {
final EntitySelectorArgumentResolver entitySelectorArgumentResolver = ctx.getArgument("arg", EntitySelectorArgumentResolver.class);
final List entities = entitySelectorArgumentResolver.resolve(ctx.getSource());
final Component foundEntities = Component.join(JoinConfiguration.commas(true), entities.stream().map(Entity::name).toList());
ctx.getSource().getSender().sendRichMessage("Found ",
Placeholder.component("entitynames", foundEntities)
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## Player argument
The player argument allows to retrieve a `PlayerSelectorArgumentResolver` for player arguments.
For this "single player" argument, you can safely get the target player by running `PlayerSelectorArgumentResolver.resolve(ctx.getSource()).getFirst()`,
which returns a [Player](jd:paper:org.bukkit.entity.Player) object.
### Example usage
This command yeets the targeted player into the air!
```java
public static LiteralCommandNode playerArgument() {
return Commands.literal("player")
.then(Commands.argument("target", ArgumentTypes.player())
.executes(ctx -> {
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("target", PlayerSelectorArgumentResolver.class);
final Player target = targetResolver.resolve(ctx.getSource()).getFirst();
target.setVelocity(new Vector(0, 100, 0));
target.sendRichMessage("Yeeeeeeeeeet");
ctx.getSource().getSender().sendRichMessage("Yeeted !",
Placeholder.component("target", target.name())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## Players argument
The "multiple players" argument works similarly to the "single player" argument, also returning a `PlayerSelectorArgumentResolver`. Instead of just resolving to exactly one `Player`, this
one can resolve to more than just one player - which you should account for in case of using this argument. `PlayerSelectorArgumentResolver.resolve(ctx.getSource())` returns a
`List`, which you can just iterate through.
### Example usage
Extending the "single player" yeet command to support multiple targets can look like this:
```java
public static LiteralCommandNode playersArgument() {
return Commands.literal("players")
.then(Commands.argument("targets", ArgumentTypes.players())
.executes(ctx -> {
final PlayerSelectorArgumentResolver targetResolver = ctx.getArgument("targets", PlayerSelectorArgumentResolver.class);
final List targets = targetResolver.resolve(ctx.getSource());
final CommandSender sender = ctx.getSource().getSender();
for (final Player target : targets) {
target.setVelocity(new Vector(0, 100, 0));
target.sendRichMessage("Yeeeeeeeeeet");
sender.sendRichMessage("Yeeted !",
Placeholder.component("target", target.name())
);
}
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## Player profiles argument
The player profiles argument is a very powerful argument which can retrieve both offline and online players. It returns the result of the argument as a `PlayerProfileListResolver`,
which resolves to a `Collection`. This collection can be iterated to get the resulting profile(s). Usually, it only returns a single `PlayerProfile` if retrieving
a player by name, but it can return multiple if using the entity selectors (like `@a` on online players). Thus it always makes sense to run whatever operation you want to run on
all entries in the collection instead of just the first one.
This argument will run API calls to Mojang servers in order to retrieve player information for players which have never joined the server before. Due to this operation sometimes
taking a bit longer, it is suggested to resolve this argument in an asynchronous context in order to not cause any server lag.
Sometimes, these API calls may fail. This is also visible in the in-game preview down below. This behavior is also the reason for `/whitelist add` sometimes.
### Example usage - player lookup command
```java
public static LiteralCommandNode playerProfilesArgument() {
return Commands.literal("lookup")
.then(Commands.argument("profile", ArgumentTypes.playerProfiles())
.executes(ctx -> {
final PlayerProfileListResolver profilesResolver = ctx.getArgument("profile", PlayerProfileListResolver.class);
final Collection foundProfiles = profilesResolver.resolve(ctx.getSource());
for (final PlayerProfile profile : foundProfiles) {
ctx.getSource().getSender().sendPlainMessage("Found " + profile.getName());
}
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
---
# Enums
Documentation for EntityAnchor, GameMode and similar enum value arguments.
import EntityAnchorMp4 from "./assets/vanilla-arguments/entityanchor.mp4?url";
import GameModeMp4 from "./assets/vanilla-arguments/gamemode.mp4?url";
import HeightMapMp4 from "./assets/vanilla-arguments/heightmap.mp4?url";
import ScoreboardDisplaySlotMp4 from "./assets/vanilla-arguments/scoreboarddisplayslot.mp4?url";
import TemplateMirrorMp4 from "./assets/vanilla-arguments/templatemirror.mp4?url";
import TemplateRotationMp4 from "./assets/vanilla-arguments/templaterotation.mp4?url";
import Video from "/src/components/Video.astro";
## Entity anchor argument
The entity anchor argument has two valid inputs: `feet` and `eyes`. The resulting [`LookAnchor`](jd:paper:io.papermc.paper.entity.LookAnchor) is mainly used for methods like
[`Entity#lookAt(Position, LookAnchor)`](jd:paper:org.bukkit.entity.Entity#lookAt(io.papermc.paper.math.Position,io.papermc.paper.entity.LookAnchor)) or
[`Player#lookAt(Entity, LookAnchor, LookAnchor)`](jd:paper:org.bukkit.entity.Player#lookAt(org.bukkit.entity.Entity,io.papermc.paper.entity.LookAnchor,io.papermc.paper.entity.LookAnchor)).
### Example usage
```java
public static LiteralCommandNode entityAnchorArgument() {
return Commands.literal("entityanchor")
.then(Commands.argument("arg", ArgumentTypes.entityAnchor())
.executes(ctx -> {
final LookAnchor lookAnchor = ctx.getArgument("arg", LookAnchor.class);
ctx.getSource().getSender().sendRichMessage("You chose !",
Placeholder.unparsed("anchor", lookAnchor.name())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## GameMode argument
The game mode argument works the same way as the first argument of the Vanilla `/gamemode ` command. It accepts any of the 4 valid game modes, returning
a [`GameMode`](jd:paper:org.bukkit.GameMode) enum to use in code.
### Example usage
```java
public static LiteralCommandNode gameModeArgument() {
return Commands.literal("gamemodearg")
.then(Commands.argument("arg", ArgumentTypes.gameMode())
.executes(ctx -> {
final GameMode gamemode = ctx.getArgument("arg", GameMode.class);
if (ctx.getSource().getExecutor() instanceof Player player) {
player.setGameMode(gamemode);
player.sendRichMessage("Your gamemode has been set to !",
Placeholder.component("gamemode", Component.translatable(gamemode))
);
return Command.SINGLE_SUCCESS;
}
ctx.getSource().getSender().sendPlainMessage("This command requires a player!");
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## HeightMap argument
The [`HeightMap`](jd:paper:org.bukkit.HeightMap) argument consists of the following, valid inputs: `motion_blocking`, `motion_blocking_no_leaves`, `ocean_floor`, and `world_surface`. It is often
used for declaring relative positioning for data packs or the `/execute positioned over ` command. E.g. `world_surface`
would mean that the Y coordinate of the surface of the world on the set X/Z values should be used.
### Example usage
```java
public static LiteralCommandNode heightMapArgument() {
return Commands.literal("heightmap")
.then(Commands.argument("arg", ArgumentTypes.heightMap())
.executes(ctx -> {
final HeightMap heightMap = ctx.getArgument("arg", HeightMap.class);
ctx.getSource().getSender().sendRichMessage("You selected ",
Placeholder.unparsed("selection", heightMap.name())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## Scoreboard display slot argument
This argument allows you to retrieve a [`DisplaySlot`](jd:paper:org.bukkit.scoreboard.DisplaySlot) enum value from the user.
### Example usage
```java
public static LiteralCommandNode scoreboardDisplaySlotArgument() {
return Commands.literal("scoreboarddisplayslot")
.then(Commands.argument("slot", ArgumentTypes.scoreboardDisplaySlot())
.executes(ctx -> {
final DisplaySlot slot = ctx.getArgument("slot", DisplaySlot.class);
ctx.getSource().getSender().sendPlainMessage("You selected: " + slot.getId());
return Command.SINGLE_SUCCESS;
})
).build();
}
```
### In-game preview
## Template mirror argument
Here, the user has 3 valid input possibilities: `front_back`, `left_right`, and `none`. You can retrieve the result of
the argument as a [`Mirror`](jd:paper:org.bukkit.block.structure.Mirror) enum value.
### Example usage
```java
public static LiteralCommandNode templateMirrorArgument() {
return Commands.literal("templatemirror")
.then(Commands.argument("mirror", ArgumentTypes.templateMirror())
.executes(ctx -> {
final Mirror mirror = ctx.getArgument("mirror", Mirror.class);
ctx.getSource().getSender().sendPlainMessage("You selected: " + mirror.name());
return Command.SINGLE_SUCCESS;
})
).build();
}
```
### In-game preview
## Template rotation argument
For the template rotation argument, the user has 4 valid input possibilities: `180`, `clockwise_90`, `counterclockwise_90`, and `none`. You can retrieve the result
of the argument as a [`StructureRotation`](jd:paper:org.bukkit.block.structure.StructureRotation) enum value.
### Example usage
```java
public static LiteralCommandNode templateRotationArgument() {
return Commands.literal("templaterotation")
.then(Commands.argument("rotation", ArgumentTypes.templateRotation())
.executes(ctx -> {
final StructureRotation rotation = ctx.getArgument("rotation", StructureRotation.class);
ctx.getSource().getSender().sendPlainMessage("You selected: " + rotation.name());
return Command.SINGLE_SUCCESS;
})
).build();
}
```
### In-game preview
---
# Location
BlockPosition, FinePosition and World argument documentation.
import BlockPositionMp4 from "./assets/vanilla-arguments/blockposition.mp4?url";
import FinePositionMp4 from "./assets/vanilla-arguments/fineposition.mp4?url";
import WorldMp4 from "./assets/vanilla-arguments/world.mp4?url";
import Video from "/src/components/Video.astro";
## Block position argument
The block position argument is used for retrieving the position of a block. It works the same way as the first argument of the `/setblock ` Vanilla command.
In order to retrieve the `BlockPosition` variable from the
[`BlockPositionResolver`](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver), we have to resolve it using the command source.
### Example usage
```java
public static LiteralCommandNode blockPositionArgument() {
return Commands.literal("blockpositionargument")
.then(Commands.argument("arg", ArgumentTypes.blockPosition())
.executes(ctx -> {
final BlockPositionResolver blockPositionResolver = ctx.getArgument("arg", BlockPositionResolver.class);
final BlockPosition blockPosition = blockPositionResolver.resolve(ctx.getSource());
ctx.getSource().getSender().sendPlainMessage("Put in " + blockPosition.x() + " " + blockPosition.y() + " " + blockPosition.z());
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## Fine position argument
The fine position argument works similarly to the block position argument, with the only difference being that it can accept decimal (precise) location input. The optional
overload (`ArgumentTypes.finePosition(boolean centerIntegers)`), which defaults to false if not set, will center whole input, meaning 5 becomes 5.5 (5.0 would stay as 5.0 though),
as that is the "middle" of a block. This only applies to X/Z. The y coordinate is untouched by this operation.
This argument returns a [`FinePositionResolver`](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver). You can resolve that by running `FinePositionResolver#resolve(CommandSourceStack)` to get the resulting
[`FinePosition`](jd:paper:io.papermc.paper.math.FinePosition).
### Example usage
```java
public static LiteralCommandNode finePositionArgument() {
return Commands.literal("fineposition")
.then(Commands.argument("arg", ArgumentTypes.finePosition(true))
.executes(ctx -> {
final FinePositionResolver resolver = ctx.getArgument("arg", FinePositionResolver.class);
final FinePosition finePosition = resolver.resolve(ctx.getSource());
ctx.getSource().getSender().sendRichMessage("Position: ",
Placeholder.unparsed("x", Double.toString(finePosition.x())),
Placeholder.unparsed("y", Double.toString(finePosition.y())),
Placeholder.unparsed("z", Double.toString(finePosition.z()))
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## World argument
This argument allows the user to select one of the currently loaded world. You can retrieve the result of that as a generic Bukkit
[`World`](jd:paper:org.bukkit.World) object.
### Example usage
```java
public static LiteralCommandNode worldArgument() {
return Commands.literal("teleport-to-world")
.then(Commands.argument("world", ArgumentTypes.world())
.executes(ctx -> {
final World world = ctx.getArgument("world", World.class);
if (ctx.getSource().getExecutor() instanceof Player player) {
player.teleport(world.getSpawnLocation(), PlayerTeleportEvent.TeleportCause.COMMAND);
ctx.getSource().getSender().sendRichMessage("Successfully teleported to ",
Placeholder.component("player", player.name()),
Placeholder.unparsed("world", world.getName())
);
return Command.SINGLE_SUCCESS;
}
ctx.getSource().getSender().sendRichMessage("This command requires a player!");
return Command.SINGLE_SUCCESS;
})
).build();
}
```
### In-game preview
---
# Minecraft-specific
Everything regarding the essential Brigadier arguments.
The [Arguments and Literals](/paper/dev/command-api/basics/arguments-and-literals) page covers the most used, native Brigadier arguments. But Minecraft (and Paper) define a few more. These can be accessed
in a static context using the [`ArgumentTypes`](jd:paper:io.papermc.paper.command.brigadier.argument.ArgumentTypes) class. We will go over all of those in this section.
## Quick overview
A quick overview of all possible arguments is defined here:
| Method Name | Return Value | Quick Link |
|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| `blockPosition()` | [BlockPositionResolver](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver) | [Block Position Argument](/paper/dev/command-api/arguments/location#block-position-argument) |
| `blockState()` | [BlockState](jd:paper:org.bukkit.block.BlockState) | [Block State Argument](/paper/dev/command-api/arguments/paper#block-state-argument) |
| `component()` | [Component (Kyori)](jd:adventure:net.kyori.adventure.text.Component) | [Component Argument](/paper/dev/command-api/arguments/adventure#component-argument) |
| `doubleRange()` | [DoubleRangeProvider](jd:paper:io.papermc.paper.command.brigadier.argument.range.DoubleRangeProvider) | [Double Range argument](/paper/dev/command-api/arguments/predicate#double-range-argument) |
| `entity()` | [EntitySelectorArgumentResolver](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver) | [Entity Argument](/paper/dev/command-api/arguments/entity-player#entity-argument) |
| `entities()` | [EntitySelectorArgumentResolver](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver) | [Entities Argument](/paper/dev/command-api/arguments/entity-player#entities-argument) |
| `entityAnchor()` | [LookAnchor](jd:paper:io.papermc.paper.entity.LookAnchor) | [Entity Anchor Argument](/paper/dev/command-api/arguments/enums#entity-anchor-argument) |
| `finePosition(boolean centerIntegers)` | [FinePositionResolver](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver) | [Fine Position Argument](/paper/dev/command-api/arguments/location#fine-position-argument) |
| `gameMode()` | [GameMode](jd:paper:org.bukkit.GameMode) | [GameMode Argument](/paper/dev/command-api/arguments/enums#gamemode-argument) |
| `heightMap()` | [HeightMap](jd:paper:org.bukkit.HeightMap) | [HeightMap Argument](/paper/dev/command-api/arguments/enums#heightmap-argument) |
| `integerRange()` | [IntegerRangeProvider](jd:paper:io.papermc.paper.command.brigadier.argument.range.IntegerRangeProvider) | [Integer Range Argument](/paper/dev/command-api/arguments/predicate#integer-range-argument) |
| `itemPredicate()` | [ItemStackPredicate](jd:paper:io.papermc.paper.command.brigadier.argument.predicate.ItemStackPredicate) | [Item Predicate Argument](/paper/dev/command-api/arguments/predicate#item-predicate-argument) |
| `itemStack()` | [ItemStack](jd:paper:org.bukkit.inventory.ItemStack) | [ItemStack Argument](/paper/dev/command-api/arguments/paper#itemstack-argument) |
| `key()` | [Key (Kyori)](jd:adventure:net.kyori.adventure.key:net.kyori.adventure.key.Key) | [Key Argument](/paper/dev/command-api/arguments/adventure#key-argument) |
| `namedColor()` | [NamedTextColor (Kyori)](jd:adventure:net.kyori.adventure.text.format.NamedTextColor) | [Named Color Argument](/paper/dev/command-api/arguments/adventure#named-color-argument) |
| `namespacedKey()` | [NamespacedKey](jd:paper:org.bukkit.NamespacedKey) | [Bukkit NamespacedKey Argument](/paper/dev/command-api/arguments/paper#namespacedkey-argument) |
| `objectiveCriteria()` | [Criteria](jd:paper:org.bukkit.scoreboard.Criteria) | [Objective Criteria Argument](/paper/dev/command-api/arguments/paper#objective-criteria-argument) |
| `player()` | [PlayerSelectorArgumentResolver](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver) | [Player Argument](/paper/dev/command-api/arguments/entity-player#player-argument) |
| `players()` | [PlayerSelectorArgumentResolver](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver) | [Players Argument](/paper/dev/command-api/arguments/entity-player#players-argument) |
| `playerProfiles()` | [PlayerProfileListResolver](jd:paper:io.papermc.paper.command.brigadier.argument.resolvers.PlayerProfileListResolver) | [Player Profiles Argument](/paper/dev/command-api/arguments/entity-player#player-profiles-argument) |
| `resource(RegistryKey)` | (Depends on RegistryKey) | [Resource Argument](/paper/dev/command-api/arguments/registry#resource-argument) |
| `resourceKey(RegistryKey)` | (Depends on RegistryKey) | [Resource Key Argument](/paper/dev/command-api/arguments/registry#resource-key-argument) |
| `style()` | [Style (Kyori)](jd:adventure:net.kyori.adventure.text.format.Style) | [Style Argument](/paper/dev/command-api/arguments/adventure#adventure-style-argument) |
| `signedMessage()` | [SignedMessageResolver](jd:paper:io.papermc.paper.command.brigadier.argument.SignedMessageResolver) | [Signed Message Argument](/paper/dev/command-api/arguments/adventure#signed-message-argument) |
| `scoreboardDisplaySlot()` | [DisplaySlot](jd:paper:org.bukkit.scoreboard.DisplaySlot) | [Scoreboard Display Slot Argument](/paper/dev/command-api/arguments/enums#scoreboard-display-slot-argument) |
| `time(int mintime)` | Integer | [Time Argument](/paper/dev/command-api/arguments/paper#time-argument) |
| `templateMirror()` | [Mirror](jd:paper:org.bukkit.block.structure.Mirror) | [Template Mirror Argument](/paper/dev/command-api/arguments/enums#template-mirror-argument) |
| `templateRotation()` | [StructureRotation](jd:paper:org.bukkit.block.structure.StructureRotation) | [Template Rotation Argument](/paper/dev/command-api/arguments/enums#template-rotation-argument) |
| `uuid()` | UUID | [UUID Argument](/paper/dev/command-api/arguments/paper#uuid-argument) |
| `world()` | [World](jd:paper:org.bukkit.World) | [World Argument](/paper/dev/command-api/arguments/location#world-argument) |
---
# Paper-specific
Documentation for arguments handling miscellaneous Paper API values.
import BlockStateMp4 from "./assets/vanilla-arguments/blockstate.mp4?url";
import ItemStackMp4 from "./assets/vanilla-arguments/itemstack.mp4?url";
import NamespacedKeyMp4 from "./assets/vanilla-arguments/namespacedkey.mp4?url";
import TimeMp4 from "./assets/vanilla-arguments/time.mp4?url";
import UuidMp4 from "./assets/vanilla-arguments/uuid.mp4?url";
import ObjectiveCriteriaMp4 from "./assets/vanilla-arguments/objectivecriteria.mp4?url";
import Video from "/src/components/Video.astro";
The arguments in this section return objects frequently used in Paper API.
## Block state argument
The block state argument can be used for getting a block type and explicit, associated data.
### Example usage
```java
public static LiteralCommandNode blockStateArgument() {
return Commands.literal("blockstateargument")
.then(Commands.argument("arg", ArgumentTypes.blockState())
.executes(ctx -> {
final BlockState blockState = ctx.getArgument("arg", BlockState.class);
ctx.getSource().getSender().sendPlainMessage("You specified a " + blockState.getType() + "!");
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## ItemStack argument
The item stack argument is the way to retrieve an [`ItemStack`](jd:paper:org.bukkit.inventory.ItemStack) following the same argument format as the Vanilla `/give - []`
command as its second argument. The user may also define components to further customize the `ItemStack`. If you only require a [`Material`](jd:paper:org.bukkit.Material), you should instead
check out the [registry arguments](/paper/dev/command-api/arguments/registry).
### Example usage
```java
public static LiteralCommandNode itemStackArgument() {
return Commands.literal("itemstack")
.then(Commands.argument("stack", ArgumentTypes.itemStack())
.executes(ctx -> {
final ItemStack itemStack = ctx.getArgument("stack", ItemStack.class);
if (ctx.getSource().getExecutor() instanceof Player player) {
player.getInventory().addItem(itemStack);
ctx.getSource().getSender().sendRichMessage("Successfully gave a
- ",
Placeholder.component("player", player.name()),
Placeholder.component("item", Component.translatable(itemStack))
);
return Command.SINGLE_SUCCESS;
}
ctx.getSource().getSender().sendRichMessage("This argument requires a player!");
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## NamespacedKey argument
This argument allows the user to provide any artificial (namespaced) key. The return value of this argument is a
[`NamespacedKey`](jd:paper:org.bukkit.NamespacedKey), which makes it useful when dealing with Bukkit API.
### Example usage
```java
public static LiteralCommandNode namespacedKeyArgument() {
return Commands.literal("namespacedkey")
.then(Commands.argument("key", ArgumentTypes.namespacedKey())
.executes(ctx -> {
final NamespacedKey key = ctx.getArgument("key", NamespacedKey.class);
ctx.getSource().getSender().sendRichMessage("You put in !",
Placeholder.unparsed("key", key.toString())
);
return Command.SINGLE_SUCCESS;
}))
.build();
}
```
### In-game preview
## Time argument
The time argument allows the user to define a time frame, similar to the Vanilla `/time