Skip to main content
This guide demonstrates how to create, trigger, and manage popup notifications using the BetterHud API.

Understanding Popups

Popups are temporary notifications that appear on the player’s screen. They’re perfect for:
  • Achievement notifications
  • Damage/healing indicators
  • Quest updates
  • Custom events and alerts
  • Player interactions

Getting Popup Manager

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.manager.PopupManager;
import kr.toxicity.hud.api.popup.Popup;

import java.util.Set;

public class PopupHelper {
    
    public static PopupManager getManager() {
        return BetterHudAPI.inst().getPopupManager();
    }
    
    public static Popup getPopup(String name) {
        return getManager().getPopup(name);
    }
    
    public static Set<String> getAllPopupNames() {
        return getManager().getAllNames();
    }
    
    public static Set<Popup> getAllPopups() {
        return getManager().getAllPopups();
    }
    
    public static boolean popupExists(String name) {
        return getManager().getPopup(name) != null;
    }
}

Showing Basic Popups

Simple Popup Display

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.manager.PlayerManager;
import kr.toxicity.hud.api.manager.PopupManager;
import kr.toxicity.hud.api.player.HudPlayer;
import kr.toxicity.hud.api.popup.Popup;
import kr.toxicity.hud.api.update.UpdateEvent;
import org.bukkit.entity.Player;

import java.util.HashMap;
import java.util.Map;

public class BasicPopup {
    
    public static void showSimplePopup(Player player, String popupName) {
        PlayerManager playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) {
            return;
        }
        
        PopupManager popupManager = BetterHudAPI.inst().getPopupManager();
        Popup popup = popupManager.getPopup(popupName);
        
        if (popup == null) {
            player.sendMessage("Popup not found: " + popupName);
            return;
        }
        
        try {
            // Show popup with empty variables
            UpdateEvent event = UpdateEvent.builder().build();
            popup.show(hudPlayer, event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void showPopupWithText(Player player, String popupName, String message) {
        PlayerManager playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        PopupManager popupManager = BetterHudAPI.inst().getPopupManager();
        Popup popup = popupManager.getPopup(popupName);
        
        if (popup == null) return;
        
        try {
            Map<String, String> variables = new HashMap<>();
            variables.put("message", message);
            
            UpdateEvent event = UpdateEvent.builder()
                .variables(variables)
                .build();
            
            popup.show(hudPlayer, event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Dynamic Data Popups

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.player.HudPlayer;
import kr.toxicity.hud.api.popup.Popup;
import kr.toxicity.hud.api.update.UpdateEvent;
import org.bukkit.entity.Player;

import java.util.HashMap;
import java.util.Map;

public class DynamicPopup {
    
    public static void showDamagePopup(Player player, double damage, String damageType) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        var popupManager = BetterHudAPI.inst().getPopupManager();
        Popup popup = popupManager.getPopup("damage_indicator");
        
        if (popup == null) return;
        
        try {
            Map<String, String> variables = new HashMap<>();
            variables.put("damage", String.format("%.1f", damage));
            variables.put("damage_type", damageType);
            variables.put("is_critical", String.valueOf(damage > 10));
            
            UpdateEvent event = UpdateEvent.builder()
                .variables(variables)
                .build();
            
            popup.show(hudPlayer, event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void showLevelUpPopup(Player player, int oldLevel, int newLevel) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        var popupManager = BetterHudAPI.inst().getPopupManager();
        Popup popup = popupManager.getPopup("level_up");
        
        if (popup == null) return;
        
        try {
            Map<String, String> variables = new HashMap<>();
            variables.put("old_level", String.valueOf(oldLevel));
            variables.put("new_level", String.valueOf(newLevel));
            variables.put("gained", String.valueOf(newLevel - oldLevel));
            
            UpdateEvent event = UpdateEvent.builder()
                .variables(variables)
                .build();
            
            popup.show(hudPlayer, event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void showQuestPopup(Player player, String questName, int progress, int total) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        var popupManager = BetterHudAPI.inst().getPopupManager();
        Popup popup = popupManager.getPopup("quest_update");
        
        if (popup == null) return;
        
        try {
            Map<String, String> variables = new HashMap<>();
            variables.put("quest_name", questName);
            variables.put("progress", String.valueOf(progress));
            variables.put("total", String.valueOf(total));
            variables.put("percent", String.valueOf((progress * 100) / total));
            variables.put("is_complete", String.valueOf(progress >= total));
            
            UpdateEvent event = UpdateEvent.builder()
                .variables(variables)
                .build();
            
            popup.show(hudPlayer, event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Custom Popup Events (Bukkit)

Triggering via CustomPopupEvent

import kr.toxicity.hud.api.bukkit.event.CustomPopupEvent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLevelChangeEvent;

public class PopupEventListener implements Listener {
    
    @EventHandler
    public void onLevelChange(PlayerLevelChangeEvent event) {
        Player player = event.getPlayer();
        int oldLevel = event.getOldLevel();
        int newLevel = event.getNewLevel();
        
        if (newLevel > oldLevel) {
            // Create custom popup event
            CustomPopupEvent popupEvent = new CustomPopupEvent(
                player,
                "level_up_notification"
            );
            
            // Add variables
            popupEvent.getVariables().put("old_level", String.valueOf(oldLevel));
            popupEvent.getVariables().put("new_level", String.valueOf(newLevel));
            popupEvent.getVariables().put("difference", String.valueOf(newLevel - oldLevel));
            
            // Call the event
            Bukkit.getPluginManager().callEvent(popupEvent);
        }
    }
    
    // Custom achievement event
    public static void triggerAchievementPopup(Player player, String achievementName, int points) {
        CustomPopupEvent event = new CustomPopupEvent(
            player,
            "achievement_unlocked"
        );
        
        event.getVariables().put("achievement", achievementName);
        event.getVariables().put("points", String.valueOf(points));
        event.getVariables().put("timestamp", String.valueOf(System.currentTimeMillis()));
        
        Bukkit.getPluginManager().callEvent(event);
    }
}

Managing Popup Updaters

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.player.HudPlayer;
import kr.toxicity.hud.api.popup.PopupUpdater;
import org.bukkit.entity.Player;

import java.util.Map;

public class PopupUpdaterManager {
    
    public static void setPopupPriority(Player player, Object key, int priority) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        Map<Object, PopupUpdater> popupMap = hudPlayer.getPopupKeyMap();
        PopupUpdater updater = popupMap.get(key);
        
        if (updater != null) {
            updater.setIndex(priority);
        }
    }
    
    public static void removePopup(Player player, Object key) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        Map<Object, PopupUpdater> popupMap = hudPlayer.getPopupKeyMap();
        PopupUpdater updater = popupMap.get(key);
        
        if (updater != null) {
            updater.remove();
            popupMap.remove(key);
        }
    }
    
    public static void removeAllPopups(Player player) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        Map<Object, PopupUpdater> popupMap = hudPlayer.getPopupKeyMap();
        
        // Remove all popups
        popupMap.values().forEach(PopupUpdater::remove);
        popupMap.clear();
    }
    
    public static int getActivePopupCount(Player player) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return 0;
        
        return hudPlayer.getPopupKeyMap().size();
    }
}

Working with Popup Groups

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.player.HudPlayer;
import kr.toxicity.hud.api.popup.PopupIteratorGroup;
import org.bukkit.entity.Player;

import java.util.Map;

public class PopupGroupManager {
    
    public static PopupIteratorGroup getGroup(Player player, String groupName) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return null;
        
        Map<String, PopupIteratorGroup> groupMap = hudPlayer.getPopupGroupIteratorMap();
        return groupMap.get(groupName);
    }
    
    public static void clearGroup(Player player, String groupName) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        Map<String, PopupIteratorGroup> groupMap = hudPlayer.getPopupGroupIteratorMap();
        groupMap.remove(groupName);
    }
    
    public static void clearAllGroups(Player player) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        hudPlayer.getPopupGroupIteratorMap().clear();
    }
}

Complete Example: Combat Notification System

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.bukkit.event.CustomPopupEvent;
import kr.toxicity.hud.api.player.HudPlayer;
import kr.toxicity.hud.api.popup.Popup;
import kr.toxicity.hud.api.update.UpdateEvent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.PlayerDeathEvent;

import java.util.HashMap;
import java.util.Map;

public class CombatNotifications implements Listener {
    
    @EventHandler
    public void onPlayerDamage(EntityDamageByEntityEvent event) {
        if (!(event.getEntity() instanceof Player victim)) return;
        if (!(event.getDamager() instanceof Player attacker)) return;
        
        double damage = event.getFinalDamage();
        boolean isCritical = damage > 6.0;
        
        // Show damage dealt to attacker
        showDamageDealt(attacker, damage, isCritical);
        
        // Show damage received to victim
        showDamageReceived(victim, damage, isCritical);
    }
    
    private void showDamageDealt(Player player, double damage, boolean critical) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        var popupManager = BetterHudAPI.inst().getPopupManager();
        Popup popup = popupManager.getPopup("damage_dealt");
        
        if (popup == null) return;
        
        try {
            Map<String, String> variables = new HashMap<>();
            variables.put("damage", String.format("%.1f", damage));
            variables.put("is_critical", String.valueOf(critical));
            variables.put("damage_color", critical ? "&c" : "&e");
            
            UpdateEvent event = UpdateEvent.builder()
                .variables(variables)
                .build();
            
            popup.show(hudPlayer, event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private void showDamageReceived(Player player, double damage, boolean critical) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        var popupManager = BetterHudAPI.inst().getPopupManager();
        Popup popup = popupManager.getPopup("damage_received");
        
        if (popup == null) return;
        
        try {
            Map<String, String> variables = new HashMap<>();
            variables.put("damage", String.format("%.1f", damage));
            variables.put("is_critical", String.valueOf(critical));
            variables.put("remaining_health", String.format("%.1f", player.getHealth() - damage));
            
            UpdateEvent event = UpdateEvent.builder()
                .variables(variables)
                .build();
            
            popup.show(hudPlayer, event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    @EventHandler
    public void onPlayerDeath(PlayerDeathEvent event) {
        Player player = event.getEntity();
        Player killer = player.getKiller();
        
        if (killer != null) {
            // Show kill notification to killer
            CustomPopupEvent killEvent = new CustomPopupEvent(
                killer,
                "player_kill"
            );
            killEvent.getVariables().put("victim", player.getName());
            killEvent.getVariables().put("killstreak", "5"); // Example
            
            Bukkit.getPluginManager().callEvent(killEvent);
        }
        
        // Show death notification to victim
        CustomPopupEvent deathEvent = new CustomPopupEvent(
            player,
            "player_death"
        );
        deathEvent.getVariables().put("killer", killer != null ? killer.getName() : "Unknown");
        
        Bukkit.getPluginManager().callEvent(deathEvent);
    }
}

Advanced: Timed Popup Queue

import kr.toxicity.hud.api.BetterHudAPI;
import kr.toxicity.hud.api.player.HudPlayer;
import kr.toxicity.hud.api.popup.Popup;
import kr.toxicity.hud.api.update.UpdateEvent;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

public class PopupQueue {
    
    private static final Map<UUID, Queue<PopupData>> playerQueues = new HashMap<>();
    private static final Map<UUID, Boolean> processingQueues = new HashMap<>();
    
    public static void queuePopup(Player player, String popupName, Map<String, String> variables) {
        UUID uuid = player.getUniqueId();
        
        playerQueues.computeIfAbsent(uuid, k -> new ConcurrentLinkedQueue<>())
            .add(new PopupData(popupName, variables));
        
        // Start processing if not already processing
        if (!processingQueues.getOrDefault(uuid, false)) {
            processQueue(player);
        }
    }
    
    private static void processQueue(Player player) {
        UUID uuid = player.getUniqueId();
        Queue<PopupData> queue = playerQueues.get(uuid);
        
        if (queue == null || queue.isEmpty()) {
            processingQueues.put(uuid, false);
            return;
        }
        
        processingQueues.put(uuid, true);
        PopupData data = queue.poll();
        
        if (data != null) {
            showPopupNow(player, data.popupName, data.variables);
            
            // Process next popup after delay
            new BukkitRunnable() {
                @Override
                public void run() {
                    processQueue(player);
                }
            }.runTaskLater(JavaPlugin.getProvidingPlugin(PopupQueue.class), 40L); // 2 second delay
        }
    }
    
    private static void showPopupNow(Player player, String popupName, Map<String, String> variables) {
        var playerManager = BetterHudAPI.inst().getPlayerManager();
        HudPlayer hudPlayer = playerManager.getHudPlayer(player.getUniqueId());
        
        if (hudPlayer == null) return;
        
        var popupManager = BetterHudAPI.inst().getPopupManager();
        Popup popup = popupManager.getPopup(popupName);
        
        if (popup == null) return;
        
        try {
            UpdateEvent event = UpdateEvent.builder()
                .variables(variables != null ? variables : new HashMap<>())
                .build();
            
            popup.show(hudPlayer, event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void clearQueue(Player player) {
        UUID uuid = player.getUniqueId();
        Queue<PopupData> queue = playerQueues.get(uuid);
        if (queue != null) {
            queue.clear();
        }
        processingQueues.put(uuid, false);
    }
    
    private static class PopupData {
        final String popupName;
        final Map<String, String> variables;
        
        PopupData(String popupName, Map<String, String> variables) {
            this.popupName = popupName;
            this.variables = variables;
        }
    }
}

Best Practices

  • Don’t spam popups - use queues for multiple notifications
  • Cache Popup instances instead of repeatedly fetching them
  • Use meaningful keys for PopupUpdater management
  • Clear old popups that are no longer needed
  • Use consistent variable names across popups
  • Validate variable values before setting them
  • Document expected variables for each popup
  • Use default values in your popup configurations
  • Always check if popup exists before showing
  • Validate HudPlayer is not null
  • Catch exceptions when showing popups
  • Log errors for debugging

Next Steps

Custom HUD

Create persistent HUD elements

Update Events

Understanding update events

Popup Manager

Complete Popup Manager API

Bukkit Events

BetterHud Bukkit events

Build docs developers (and LLMs) love