Titles, action bars, boss bars and sounds
Show big titles, quick status lines and progress bars, and play sounds to players.
Chat messages are easy to miss. On this page you will show big titles in the middle of the screen, quick status lines above the hotbar, progress bars at the top, and play sounds, and then combine all four into a working /countdown command.
Four ways to get a player's attention
Everything on this page is something Minecraft already knows how to draw or play. Your plugin only has to ask for it. Each one has a different job:
| What | Best for | How long it stays |
|---|---|---|
| Title and subtitle | Big moments: "Welcome!", "Round 2", "You died" | A few seconds, then fades. You choose the timing. |
| Action bar | Short, changing status: cooldowns, health, money, damage numbers | About three seconds. Send it again to keep it. |
| Boss bar | Progress or time left: events, countdowns, loading | Until you hide it. |
| Sound | Feedback: success, error, tick, warning | As long as the sound itself. |
All four work on any audience (a player, or the whole server), just like sendMessage did on the previous page. The text parts take components, so you can use colors, bold and MiniMessage in all of them.
To try everything, this page uses a small plugin with a /tour command. Type /tour title, /tour actionbar, /tour bossbar or /tour sound and your screen shows the example. Here is the command class; the next sections explain one method at a time.
Where this file livestitles-actionbar-bossbar-sounds-toursrcmainjavacomexampletitlesactionbarbossbarsoundstourTourCommand.java
The package com.example.titlesactionbarbossbarsoundstour is the folder path com/example/titlesactionbarbossbarsoundstour inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-tour/
- src/main/
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- TourCommand.javayou are hereCommand (BasicCommand)
- TourPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
28@Override29public void execute(CommandSourceStack source, String[] args) {30 if (!(source.getExecutor() instanceof Player player)) {31 source.getSender().sendRichMessage("<red>Only players have a screen.");32 return;33 }34 if (args.length == 0) {35 player.sendRichMessage("<gray>Usage: /tour <white>title, actionbar, bossbar, sound</white> or <white>clear</white>");36 return;37 }38 39 switch (args[0].toLowerCase()) {40 case "title" -> showTitle(player);41 case "actionbar" -> showActionBar(player);42 case "bossbar" -> showBossBar(player);43 case "sound" -> playSounds(player);44 case "orb" -> playOrbSound(player);45 case "clear" -> player.clearTitle();46 default -> player.sendRichMessage("<red>Unknown option. Try title, actionbar, bossbar, sound or clear.");47 }48}- Titles and boss bars are drawn on one player's screen. The console has no screen, so we stop and say so. This check also gives us the variable
player. - The player typed just
/tour. Show how to use it. - Picks one branch by the first word the player typed.
toLowerCase()makesTITLEandtitlemean the same. The arrow formcase "x" -> ...is the modernswitch; Making decisions explains it. - Runs when the word matches none of the cases.
Titles
A title is the big text in the middle of the screen, with a smaller subtitle underneath. It appears, stays for a while and fades away.
Where this file livestitles-actionbar-bossbar-sounds-toursrcmainjavacomexampletitlesactionbarbossbarsoundstourTourCommand.java
The package com.example.titlesactionbarbossbarsoundstour is the folder path com/example/titlesactionbarbossbarsoundstour inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-tour/
- src/main/
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- TourCommand.javayou are hereCommand (BasicCommand)
- TourPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
59private void showTitle(Player player) {60 Title.Times times = Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500));61 Title title = Title.title(62 Component.text("Welcome!", NamedTextColor.GOLD, TextDecoration.BOLD),63 Component.text("Enjoy your stay, " + player.getName(), NamedTextColor.GRAY),64 times);65 player.showTitle(title);66}- The timing, built from three
Durationvalues. ADuration is Java's way to say "this long":Duration.ofSeconds(3),Duration.ofMillis(500). - The order is always fade in, stay, fade out.
- Bundles the big text, the subtitle and the timing into one
Titleobject. Both texts are components, so they can have any color or style. - Shows it on this player's screen.
Timing
The game counts time in ticks: 20 ticks make one second (see Timing and tasks). Adventure's Title.Times uses Duration instead, so you can write seconds directly. If you have a tick number, Ticks.duration(10) from net.kyori.adventure.util.Ticks converts it.
If you leave the times out, the default is used: Title.title(title, subtitle) fades in for 10 ticks, stays 70 ticks and fades out for 20 ticks.
Title only, subtitle only
Pass Component.empty() for the part you do not want. A title with only a subtitle is a nice, quiet way to say something:
1Title subtitleOnly = Title.title(Component.empty(), Component.text("Round 2", NamedTextColor.AQUA));2player.showTitle(subtitleOnly);Clearing a title
player.clearTitle()removes the title that is on screen right now.player.resetTitle()also puts the timing back to the default values.
Try /tour title and then quickly /tour clear to see the title vanish.
The action bar
The action bar is the single line just above the hotbar. Minecraft uses it for "Now playing: a song". It is perfect for small, changing numbers.
Where this file livestitles-actionbar-bossbar-sounds-toursrcmainjavacomexampletitlesactionbarbossbarsoundstourTourCommand.java
The package com.example.titlesactionbarbossbarsoundstour is the folder path com/example/titlesactionbarbossbarsoundstour inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-tour/
- src/main/
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- TourCommand.javayou are hereCommand (BasicCommand)
- TourPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
68private void showActionBar(Player player) {69 player.sendActionBar(Component.text()70 .append(Component.text("Cooldown: ", NamedTextColor.GRAY))71 .append(Component.text("4.5 s", NamedTextColor.RED))72 .build());73}- Takes one component. There is no timing to give: Minecraft shows the line for about three seconds.
- The builder from the previous page: it collects the gray label and the red number into one component.
Because the line fades after about three seconds, a changing value, such as a cooldown, is shown by sending the action bar again every few ticks. A new action bar replaces the old one at once. There is no "clear" method; to hide it, send an empty component with player.sendActionBar(Component.empty()).
With MiniMessage the same line is shorter. This is what you would write for a health display:
1player.sendActionBar(MiniMessage.miniMessage().deserialize(2 "<red>Health: <white><health></white>",3 Placeholder.unparsed("health", String.valueOf(Math.round(player.getHealth())))));Boss bars
A boss bar is the bar at the top of the screen that the Ender Dragon and the Wither show. It has a name, a colored bar that is filled to some amount, and optionally notches. Plugins use it for anything with progress: event timers, capture points, loading.
Unlike a title, a boss bar stays until you hide it. It is also an object you keep and change: you create the BossBar, show it to players, change its progress later, and finally hide it.
Where this file livestitles-actionbar-bossbar-sounds-toursrcmainjavacomexampletitlesactionbarbossbarsoundstourTourCommand.java
The package com.example.titlesactionbarbossbarsoundstour is the folder path com/example/titlesactionbarbossbarsoundstour inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-tour/
- src/main/
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- TourCommand.javayou are hereCommand (BasicCommand)
- TourPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
75private void showBossBar(Player player) {76 BossBar bar = BossBar.bossBar(77 Component.text("Loading the arena...", NamedTextColor.GREEN),78 0.0f,79 BossBar.Color.GREEN,80 BossBar.Overlay.PROGRESS);81 player.showBossBar(bar);82 83 plugin.getServer().getScheduler().runTaskTimer(plugin, task -> {84 float next = Math.min(1.0f, bar.progress() + 0.1f);85 bar.progress(next);86 if (next >= 1.0f) {87 player.hideBossBar(bar);88 task.cancel();89 }90 }, 10L, 10L);91}- Creates the bar with four things: a name, the progress, a color and an overlay (the shape).
- Progress is a number from 0.0 (empty) to 1.0 (full). The
fmakes it afloat, Java's small decimal number. Any number outside 0 to 1 throws anIllegalArgumentException. - Pink, blue, red, green, yellow, purple or white.
- A plain bar. Others are notched into 6, 10, 12 or 20 segments.
- Puts the bar on this player's screen. The bar is empty at first.
- Starts a repeating task: after 10 ticks, and then every 10 ticks, the code in the braces runs. The part
task -> { ... }is a lambda: a small piece of code handed over to run later.taskis a handle you can use to stop the repeating task. - Takes a little step forward but never goes past 1.0, so a rounding error cannot throw an exception.
- Changes the bar. Every player who sees this bar sees it move at once, because they all share the same
BossBarobject. - Removes the bar from the screen when it is full.
- Stops the repeating task. Forget this and the task keeps running forever.
Colors and overlays
The seven colors are pink, blue, red, green, yellow, purple and white. The overlay decides whether the bar is one smooth strip (PROGRESS) or cut into 6, 10, 12 or 20 segments (NOTCHED_6 and so on). The previews below show four of the colors; the notches are only drawn by the real game.
| Setter | What it changes |
|---|---|
bar.name(component) | The text above the bar. |
bar.progress(0.5f) | How full the bar is. |
bar.color(BossBar.Color.RED) | The bar color. |
bar.overlay(BossBar.Overlay.NOTCHED_10) | Plain or notched. |
bar.addFlag(BossBar.Flag.DARKEN_SCREEN) | Optional effects: DARKEN_SCREEN, PLAY_BOSS_MUSIC and CREATE_WORLD_FOG. |
These setters change the bar in place, and everyone who is watching sees the change immediately.
Sounds
Minecraft has hundreds of sounds: a chest opening, a creeper hissing, a note block pling. Playing one needs three decisions: which sound, how (volume and pitch) and to whom.
Which sound
Paper lists every sound as a constant on org.bukkit.Sound, for example Sound.ENTITY_EXPERIENCE_ORB_PICKUP (the ding when you collect XP) or Sound.BLOCK_NOTE_BLOCK_PLING. In IntelliJ type Sound. and use autocomplete, or look names up in the Names Lookup tool. The name usually says what it is: first the source (ENTITY, BLOCK, UI, MUSIC) and then the sound.
Volume and pitch
- Volume is 1.0 for normal. Lower is quieter. Higher than 1.0 does not make the sound louder; it makes the sound reach farther, which matters when you play a sound at a place instead of straight to a player.
- Pitch is 1.0 for normal. Lower is deeper and slower, higher is squeakier and faster. The useful range is 0.5 to 2.0. This is how note blocks make different notes from one sound.
The Adventure way
Adventure describes a sound as an object with a name, a source, a volume and a pitch. The source says which of the player's volume sliders (Master, Music, Blocks, Hostile Creatures, Players and so on) controls it:
Where this file livestitles-actionbar-bossbar-sounds-toursrcmainjavacomexampletitlesactionbarbossbarsoundstourTourCommand.java
The package com.example.titlesactionbarbossbarsoundstour is the folder path com/example/titlesactionbarbossbarsoundstour inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-tour/
- src/main/
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- TourCommand.javayou are hereCommand (BasicCommand)
- TourPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
93private void playSounds(Player player) {94 float[] pitches = {0.5f, 1.0f, 2.0f};95 for (int i = 0; i < pitches.length; i++) {96 Sound sound = Sound.sound(org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING, Sound.Source.MASTER, 1.0f, pitches[i]);97 plugin.getServer().getScheduler().runTaskLater(plugin, () -> player.playSound(sound), i * 10L);98 }99}- Three pitches: low, normal and high. An
array holds several values of one type.0.5fis a decimalfloat. - Builds an Adventure sound. Two classes are both called
Sound: Adventure'snet.kyori.adventure.sound.Sound(imported in this file) and Paper'sorg.bukkit.Soundwith the constants. The code writesorg.bukkit.Soundin full for the constant so Java knows which one is meant. - Which volume slider applies.
MASTERis the general one. - Volume 1.0 and one of the three pitches.
- Runs the code once, later. The last number is the delay in ticks, so the three notes play at 0, 10 and 20 ticks.
- Plays the sound for this player only, at the player's own location.
The same playSound(Sound) method exists on every audience. To play a sound for everyone online, call it on the server: getServer().playSound(sound). Each player hears it at their own location.
The Bukkit way
Paper also keeps the classic method, which you will see in a lot of code. It plays a sound at a place in the world. Try it with /tour orb:
Where this file livestitles-actionbar-bossbar-sounds-toursrcmainjavacomexampletitlesactionbarbossbarsoundstourTourCommand.java
The package com.example.titlesactionbarbossbarsoundstour is the folder path com/example/titlesactionbarbossbarsoundstour inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-tour/
- src/main/
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- TourCommand.javayou are hereCommand (BasicCommand)
- TourPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundstour/Package com.example.titlesactionbarbossbarsoundstour
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
101private void playOrbSound(Player player) {102 player.playSound(player.getLocation(), org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.PLAYERS, 1.0f, 1.0f);103}- The place where the sound comes from. Here, the player's own position.
- The same idea as the Adventure source: which volume slider controls it.
| Adventure | Bukkit | |
|---|---|---|
| Call | player.playSound(Sound.sound(...)) | player.playSound(location, sound, category, volume, pitch) |
| Who hears it | The audience you call it on. | Only the player you call it on, but it is played from a location. world.playSound(location, ...) plays for everyone near. |
| Source or category | Sound.Source.MASTER, PLAYER, UI, ... | SoundCategory.MASTER, PLAYERS, UI, ... |
| Names | The same constants, or a custom Key. | The same constants, or a text name. |
Both end up playing the same thing. Prefer the Adventure style in new plugins because it is the same API that titles and boss bars use, and because you can build a Sound once and reuse it. To stop a sound early, call player.stopSound(sound).
Project: a /countdown command
Now we combine all four. /countdown 10 starts a ten second countdown for everyone. Every second shows a big number as a title, moves a boss bar down, and plays a pling. The last three seconds turn red and the pling gets higher. When it reaches zero, "GO!" appears with a level-up sound. /countdown stop cancels it.
The plan, as small pieces:
Create the boss bar once
One
BossBarobject is kept in a field of a class calledCountdown, so everyone shares it.Start a repeating task
A task that runs once a second (every 20 ticks) calls a method named
tick().In each tick, update everything
Count down one second, then update the bar, show a title, and play a sound.
At zero, finish
Show "GO!", play a sound, cancel the task and hide the bar.
The Countdown class
Where this file livestitles-actionbar-bossbar-sounds-countdownsrcmainjavacomexampletitlesactionbarbossbarsoundscountdownCountdown.java
The package com.example.titlesactionbarbossbarsoundscountdown is the folder path com/example/titlesactionbarbossbarsoundscountdown inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-countdown/
- src/main/
- java/com/example/titlesactionbarbossbarsoundscountdown/Package com.example.titlesactionbarbossbarsoundscountdown
- Countdown.javayou are hereHelper class
- CountdownCommand.javaCommand (BasicCommand)
- CountdownJoinListener.javaListener: reacts to events
- CountdownPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundscountdown/Package com.example.titlesactionbarbossbarsoundscountdown
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.titlesactionbarbossbarsoundscountdown;2 3import java.time.Duration;4import net.kyori.adventure.bossbar.BossBar;5import net.kyori.adventure.sound.Sound;6import net.kyori.adventure.text.Component;7import net.kyori.adventure.text.format.NamedTextColor;8import net.kyori.adventure.text.format.TextDecoration;9import net.kyori.adventure.text.minimessage.MiniMessage;10import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;11import net.kyori.adventure.title.Title;12import org.bukkit.entity.Player;13import org.bukkit.plugin.java.JavaPlugin;14import org.bukkit.scheduler.BukkitTask;15 16public final class Countdown {17 18 private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();19 private static final Title.Times ONE_SECOND_TIMES =20 Title.Times.times(Duration.ZERO, Duration.ofMillis(900), Duration.ZERO);21 22 private final JavaPlugin plugin;23 private final BossBar bossBar = BossBar.bossBar(24 Component.empty(), 1.0f, BossBar.Color.YELLOW, BossBar.Overlay.NOTCHED_10);25 26 private BukkitTask task;27 private int totalSeconds;28 private int secondsLeft;29 30 public Countdown(JavaPlugin plugin) {31 this.plugin = plugin;32 }33 34 public boolean isRunning() {35 return task != null;36 }37 38 public void start(int seconds) {39 totalSeconds = seconds;40 secondsLeft = seconds;41 bossBar.progress(1.0f);42 bossBar.color(BossBar.Color.YELLOW);43 44 for (Player player : plugin.getServer().getOnlinePlayers()) {45 player.showBossBar(bossBar);46 }47 task = plugin.getServer().getScheduler().runTaskTimer(plugin, this::tick, 0L, 20L);48 }49 50 public void stop() {51 if (task == null) {52 return;53 }54 task.cancel();55 task = null;56 plugin.getServer().hideBossBar(bossBar);57 }58 59 public void showTo(Player player) {60 if (isRunning()) {61 player.showBossBar(bossBar);62 }63 }64 65 private void tick() {66 if (secondsLeft == 0) {67 finish();68 return;69 }70 71 boolean lastSeconds = secondsLeft <= 3;72 NamedTextColor color = lastSeconds ? NamedTextColor.RED : NamedTextColor.YELLOW;73 74 bossBar.progress((float) secondsLeft / totalSeconds);75 bossBar.color(lastSeconds ? BossBar.Color.RED : BossBar.Color.YELLOW);76 bossBar.name(MINI_MESSAGE.deserialize(77 "<white>Game starts in <urgency><seconds>",78 Placeholder.styling("urgency", color),79 Placeholder.unparsed("seconds", String.valueOf(secondsLeft))));80 81 Title title = Title.title(82 Component.text(secondsLeft, color, TextDecoration.BOLD),83 Component.text("Get ready!", NamedTextColor.GRAY),84 ONE_SECOND_TIMES);85 plugin.getServer().showTitle(title);86 87 float pitch = lastSeconds ? 2.0f : 1.0f;88 plugin.getServer().playSound(89 Sound.sound(org.bukkit.Sound.BLOCK_NOTE_BLOCK_PLING, Sound.Source.MASTER, 1.0f, pitch));90 91 secondsLeft--;92 }93 94 private void finish() {95 Title title = Title.title(96 Component.text("GO!", NamedTextColor.GREEN, TextDecoration.BOLD),97 Component.empty(),98 Title.Times.times(Duration.ZERO, Duration.ofSeconds(1), Duration.ofMillis(500)));99 plugin.getServer().showTitle(title);100 plugin.getServer().playSound(101 Sound.sound(org.bukkit.Sound.ENTITY_PLAYER_LEVELUP, Sound.Source.MASTER, 1.0f, 1.0f));102 stop();103 }104}- The title timing, made once. No fade, shown for 0.9 seconds, so each new number replaces the old one before the next tick.
- The one shared bar. Name and progress are filled in on every tick, so the starting values do not matter.
- A handle to the repeating task. When it is
null, no countdown is running.BukkitTaskis what the scheduler gives back when you start a task. - How many seconds are still to go.
- Other classes ask this before starting a second countdown.
- Starting values: the bar is full and yellow, because a previous countdown may have left it red.
- Everyone who is online right now sees the bar. Players who join later are handled by a listener.
- A
method reference : "call mytickmethod". It is a shorter way to write() -> tick(). - Start now, then repeat every 20 ticks, which is every second.
- Stops the repeating task. Then the field is set to
nullsoisRunning()says false again. - Removes the bar from every player.
- True in the last three seconds. Used for the color, the bar color and the pitch.
- The progress, for example 7 of 10 seconds left is 0.7. The
(float)turns the number into a decimal first. Without it, Java would do whole-number division and the answer would be 0. - A MiniMessage placeholder that applies a color, so
<urgency>means "yellow, or red in the last seconds". MiniMessage explains placeholders. - The server is an audience, so this shows the title to every player at once.
- "If it is the last seconds, use 2.0, otherwise 1.0". The tick sound goes up in pitch to build tension.
- Counts down by one. This is the same as
secondsLeft = secondsLeft - 1. - Called when the number reaches zero. It shows "GO!" and cleans up.
This is what players see in the last seconds:
And at zero:
The command
Where this file livestitles-actionbar-bossbar-sounds-countdownsrcmainjavacomexampletitlesactionbarbossbarsoundscountdownCountdownCommand.java
The package com.example.titlesactionbarbossbarsoundscountdown is the folder path com/example/titlesactionbarbossbarsoundscountdown inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-countdown/
- src/main/
- java/com/example/titlesactionbarbossbarsoundscountdown/Package com.example.titlesactionbarbossbarsoundscountdown
- Countdown.javaHelper class
- CountdownCommand.javayou are hereCommand (BasicCommand)
- CountdownJoinListener.javaListener: reacts to events
- CountdownPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundscountdown/Package com.example.titlesactionbarbossbarsoundscountdown
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
18@Override19public void execute(CommandSourceStack source, String[] args) {20 CommandSender sender = source.getSender();21 22 if (args.length > 0 && args[0].equalsIgnoreCase("stop")) {23 if (countdown.isRunning()) {24 countdown.stop();25 sender.sendRichMessage("<yellow>Countdown stopped.");26 } else {27 sender.sendRichMessage("<red>No countdown is running.");28 }29 return;30 }31 32 int seconds = 10;33 if (args.length > 0) {34 try {35 seconds = Integer.parseInt(args[0]);36 } catch (NumberFormatException exception) {37 sender.sendRichMessage("<red>Usage: /countdown [seconds] or /countdown stop");38 return;39 }40 }41 if (seconds < 1 || seconds > MAX_SECONDS) {42 sender.sendRichMessage("<red>Pick a number of seconds from 1 to <max>.",43 Placeholder.unparsed("max", String.valueOf(MAX_SECONDS)));44 return;45 }46 if (countdown.isRunning()) {47 sender.sendRichMessage("<red>A countdown is already running. Use /countdown stop first.");48 return;49 }50 51 countdown.start(seconds);52}- The player typed
/countdown stop.equalsIgnoreCaseignores capital letters. - The default when the player types only
/countdown. - Turning text into a number can fail. Whatever goes wrong inside the braces after
tryis caught below instead of crashing the command. - Converts text such as
"10"into the number 10. If the player typedabc, it throws aNumberFormatException. - The player did not type a number. Show the usage and stop. Null, exceptions and stack traces explains
tryandcatch. - Rejects numbers that are too small or too large.
||means "or". - Do not start a second countdown on top of the first.
- Everything checked: start.
The main class creates one Countdown, registers a listener and the command, and stops the countdown if the server shuts down:
Where this file livestitles-actionbar-bossbar-sounds-countdownsrcmainjavacomexampletitlesactionbarbossbarsoundscountdownCountdownPlugin.java
The package com.example.titlesactionbarbossbarsoundscountdown is the folder path com/example/titlesactionbarbossbarsoundscountdown inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-countdown/
- src/main/
- java/com/example/titlesactionbarbossbarsoundscountdown/Package com.example.titlesactionbarbossbarsoundscountdown
- Countdown.javaHelper class
- CountdownCommand.javaCommand (BasicCommand)
- CountdownJoinListener.javaListener: reacts to events
- CountdownPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundscountdown/Package com.example.titlesactionbarbossbarsoundscountdown
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.titlesactionbarbossbarsoundscountdown;2 3import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;4import org.bukkit.plugin.java.JavaPlugin;5 6public final class CountdownPlugin extends JavaPlugin {7 8 private Countdown countdown;9 10 @Override11 public void onEnable() {12 countdown = new Countdown(this);13 14 getServer().getPluginManager().registerEvents(new CountdownJoinListener(countdown), this);15 getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->16 event.registrar().register("countdown", "Start or stop a countdown", new CountdownCommand(countdown)));17 }18 19 @Override20 public void onDisable() {21 countdown.stop();22 }23}- The one
Countdownthat the command and the listener share. It gets the plugin because the scheduler needs it. - Runs when the server stops or the plugin is disabled. We cancel the task and hide the bar so nothing is left behind.
The last piece shows the bar to players who join in the middle of a countdown:
Where this file livestitles-actionbar-bossbar-sounds-countdownsrcmainjavacomexampletitlesactionbarbossbarsoundscountdownCountdownJoinListener.java
The package com.example.titlesactionbarbossbarsoundscountdown is the folder path com/example/titlesactionbarbossbarsoundscountdown inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-countdown/
- src/main/
- java/com/example/titlesactionbarbossbarsoundscountdown/Package com.example.titlesactionbarbossbarsoundscountdown
- Countdown.javaHelper class
- CountdownCommand.javaCommand (BasicCommand)
- CountdownJoinListener.javayou are hereListener: reacts to events
- CountdownPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundscountdown/Package com.example.titlesactionbarbossbarsoundscountdown
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
15@EventHandler16public void onJoin(PlayerJoinEvent event) {17 countdown.showTo(event.getPlayer());18}- If a countdown is running, this adds the joining player as a viewer of the shared bar.
- titles-actionbar-bossbar-sounds-countdown/
- src/
- main/
- java/
- com/example/titlesactionbarbossbarsoundscountdown/
- CountdownPlugin.javaCreates the Countdown, registers the command and the listener
- Countdown.javaThe boss bar, the repeating task and the title and sounds for each second
- CountdownCommand.java/countdown [seconds] and /countdown stop
- CountdownJoinListener.javaShows the bar to players who join mid-countdown
- com/example/titlesactionbarbossbarsoundscountdown/
- resources/
- plugin.ymlPlugin name, version and the titlescountdown.use permission
- java/
- main/
- src/
You can download this project and run it. Join your test server, give yourself operator status (the permission defaults to op), and type /countdown 10.
Mistakes to avoid
- Forgetting to hide a boss bar. It stays on the screen. Hide it when you are done and in
onDisable. - Progress outside 0 to 1. Computing
done / totalwith whole numbers gives 0 or 1, and rounding can push a value to 1.0000001, which throws an exception. Use(float) done / totalandMath.minorMath.maxto keep it in range. - Forgetting to cancel the repeating task. A task that is never canceled keeps running and keeps sending packets after the countdown is over.
- Mixing up the two
Soundclasses. If you importorg.bukkit.Soundand also Adventure'sSound, Java refuses. Import one and write the other with its full name. - Setting a huge volume to make a sound louder. Volume above 1.0 only makes it audible from farther away.
- Showing a title for something small. A title blocks the middle of the screen. Use the action bar for small things.
- Sending an action bar once and expecting it to stay. It fades after about three seconds. Resend it while it matters.
Health in the action bar
Write a command /healthbar that shows the player's health in the action bar, like Health: 20 / 20, updating twice a second for ten seconds.
Hint 1
You need a repeating task that runs every 10 ticks. Count how many times it ran, and call cancel() when it reaches 20.
Hint 2
Maximum health is an attribute: player.getAttribute(Attribute.MAX_HEALTH) can be null, so check before using it. Current health is player.getHealth().
Show the solution
The command starts the task. The task class extends BukkitRunnable, a ready-made repeating task, and cancels itself when it has run 20 times or the player has left:
Where this file livestitles-actionbar-bossbar-sounds-exercisesrcmainjavacomexampletitlesactionbarbossbarsoundsexerciseHealthBarTask.java
The package com.example.titlesactionbarbossbarsoundsexercise is the folder path com/example/titlesactionbarbossbarsoundsexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-exercise/
- src/main/
- java/com/example/titlesactionbarbossbarsoundsexercise/Package com.example.titlesactionbarbossbarsoundsexercise
- ExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- HealthBarCommand.javaCommand (BasicCommand)
- HealthBarTask.javayou are hereHelper class
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundsexercise/Package com.example.titlesactionbarbossbarsoundsexercise
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
21@Override22public void run() {23 if (runsLeft == 0 || !player.isOnline()) {24 cancel();25 return;26 }27 runsLeft--;28 29 AttributeInstance maxHealth = player.getAttribute(Attribute.MAX_HEALTH);30 double max = maxHealth == null ? 20.0 : maxHealth.getValue();31 32 player.sendActionBar(MiniMessage.miniMessage().deserialize(33 "<red>Health: <white><health></white> <gray>/</gray> <white><max></white>",34 Placeholder.unparsed("health", String.valueOf(Math.round(player.getHealth()))),35 Placeholder.unparsed("max", String.valueOf(Math.round(max)))));36}- Stop after 20 runs (ten seconds), or when the player has quit, so the task never talks to someone who is gone.
- The maximum health can be missing, which is why the next line falls back to 20.
- Health is a decimal such as 18.5. This turns it into a whole number for display.
Where this file livestitles-actionbar-bossbar-sounds-exercisesrcmainjavacomexampletitlesactionbarbossbarsoundsexerciseHealthBarCommand.java
The package com.example.titlesactionbarbossbarsoundsexercise is the folder path com/example/titlesactionbarbossbarsoundsexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- titles-actionbar-bossbar-sounds-exercise/
- src/main/
- java/com/example/titlesactionbarbossbarsoundsexercise/Package com.example.titlesactionbarbossbarsoundsexercise
- ExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- HealthBarCommand.javayou are hereCommand (BasicCommand)
- HealthBarTask.javaHelper class
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/titlesactionbarbossbarsoundsexercise/Package com.example.titlesactionbarbossbarsoundsexercise
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
22new HealthBarTask(player).runTaskTimer(plugin, 0L, 10L);- Start now and repeat every 10 ticks, which is twice a second.
A warning sound
In the countdown, play a deep, low ENTITY_ENDER_DRAGON_GROWL sound when the countdown is started, using the Adventure Sound.sound method.
Hint
Add it to the end of start(int seconds). Use a pitch below 1.0 for a deeper growl, and play it on plugin.getServer().
Show the solution
1plugin.getServer().playSound(2 Sound.sound(org.bukkit.Sound.ENTITY_ENDER_DRAGON_GROWL, Sound.Source.MASTER, 0.5f, 0.7f));Volume 0.5 keeps it from being too loud, and pitch 0.7 makes it deeper.
Recap
- Titles use
player.showTitle(Title.title(title, subtitle, Title.Times.times(fadeIn, stay, fadeOut))). Without times, the defaults are 0.5, 3.5 and 1 second.clearTitle()hides it. - The action bar is
player.sendActionBar(component). It fades after about three seconds, so resend it for changing values, at most twice a second. - A boss bar is a shared object: create it with
BossBar.bossBar(name, progress, color, overlay), show it withshowBossBar, change it withprogress,nameandcolor, and alwayshideBossBarit. - Progress is a
floatfrom 0.0 to 1.0. - Sounds:
player.playSound(Sound.sound(org.bukkit.Sound.X, Sound.Source.MASTER, volume, pitch)), or the classic location version with aSoundCategory. Pitch 1.0 is normal; volume above 1.0 only widens the range. - For things that change over time, use a repeating scheduler task and cancel it when you are done.
Quick quiz
You show a boss bar for an event. When the event ends, you forget to call
hideBossBar. What happens?Boss bars have no timer. Only titles and action bars fade on their own. Hide the bar yourself, and also inonDisable.Which progress value throws an
IllegalArgumentException?Progress must be between 0.0 (empty) and 1.0 (full), both included. Anything outside throws an exception.How long does an action bar message stay visible if you never send another one?
The action bar has no timing options. To keep a line there, resend it every so often.Title.Timesbelongs to titles.What does a pitch of 2.0 do compared to 1.0?
Pitch changes how high the sound is. Volume above 1.0, not pitch, is what widens the range, and it does not make the sound louder.Why do the two lines
import org.bukkit.Sound;andimport net.kyori.adventure.sound.Sound;in the same file not work?Two imports with the same simple name clash. Both classes exist and both are useful: Adventure's builds a sound to play, and Paper's has the constants for built-in sound names.
Next steps
- Timing and tasks: the scheduler: everything about
runTaskTimerand friends. - Potion effects, particles and effects: more things to show and play.
- Scoreboards, sidebars and teams: another place to show live status.
- Project: Parkour course with timer: uses an action bar timer and sounds.