PaperMC / PaperMC/Paper

Callback API can quickly lead to memory leaks

Open
#13,236 1 comment 11 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

scope: performance status: needs triage
Dominant language
Java
Stars
12.7k
Forks
3.5k
Avg merge
3d 13h
Merged PRs (30d)
11

Description

Description

The Paper callback API can quickly lead to memory leaks. I've only looked at DialogAction.customClick with the callback parameter, which registers a new callback, but it looks like this applies to all callbacks using this system.

Context

Using the above mentioned method appears to be the most easy way to get a response from a dialog click, but doing so leads to a callback being registered, which can quickly lead to a memory leak as those callbacks (by default) are valid for 12 hours and won't automatically be invalidated if the dialog gets closed or the player quits.

Example
@EventHandler(ignoreCancelled = true)
public void onInteract(PlayerInteractEntityEvent event) {
    if (event.getHand() != EquipmentSlot.HAND) return;
    Player player = event.getPlayer();
    player.showDialog(Dialog.create(builder -> {
        builder.empty()
                .base(DialogBase.builder(Component.text("Are you sure, you want to interact with this entity?")).build())
                .type(DialogType.confirmation(
                        ActionButton.builder(Component.text("Yes, sir!"))
                                .action(DialogAction.customClick((response, audience) -> {
                                    player.setVelocity(new Vector(0, 5, 0));
                                }, ClickCallback.Options.builder().build()))
                                .build(),
                        ActionButton.builder(Component.text("Nope!"))
                                .action(DialogAction.customClick((response, audience) -> {
                                    audience.sendMessage(Component.text("ok."));
                                }, ClickCallback.Options.builder().build()))
                                .build()
                ));
    }));
}

This example demonstrates the issues I see with the current state of the callback system: 2 callbacks are registered when showing the dialog.

  1. Those callbacks are registered for 12 hours, after clicking one button the other callbacks remains registered, although the dialog gets closed.
  2. There is no way to invalidate a callback once registered (I may be wrong here, but I was not able to see any option, feel free to correct me if there is one)
  3. Even when using a single action dialog the callback won't be invalidated if the player disconnects or gets another dialog opened.

This also prevents objects (like in this case the player) from getting garbage collected. Yes: this should be avoided, but it's easy to accidentally have e.g. a player reference end up in this callback, which quickly results in a memory leak.

Improvements
  1. Add a way to invalidate a callback (e.g. to be able to invalidate callbacks onQuit or when a dialog gets closed). Maybe this is already possible, but I am unable to find the method?
  2. Specific to the dialog action callback: Maybe bind it to a player or dialog and automatically invalidate it once the dialog is closed or the player disconnected.
  3. Add a warning to the Dialog API docs that using callbacks can lead to memory leaks, when the callback holds references to big objects.

This example may be a "worst case" example, but it demonstrates how quickly you can cause a memory leak and in this case with the dialog action you can't really avoid it (yes you can avoid using the player reference, but you can't avoid the leak of callback instances because you can't invalidate them). The only option to avoid this is not using the customClick method which registers the callback and instead use the event based click detection approach.

Please let me know if there is a way to invalidate a callback manually as this would solve most of my issues.

Paper version

Tested on

1.21.10-87-main@b5b7c79 (2025-10-25T19:06:05Z) (Implementing API version 1.21.10-R0.1-SNAPSHOT)
You are running the latest version
Previous version: 1.21.10-82-1e67ca8 (MC: 1.21.10)

as well as the latest 1.21.8 build

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at DialogAction.customClick and trace the callback registration and lifetime handling described in the issue. Review the Dialog API callbacks documentation linked in the report. Done should include a clear invalidation or lifecycle behavior for callbacks, coverage for dialog closure and player disconnect cases, and documentation of any remaining retention risks.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, backend-api-design, documentation
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.