Using AI assistants well
Get useful plugin code from AI tools, check it, and learn from it instead of copying blindly.
AI chat assistants can write plugin code in seconds, and much of it is wrong in ways that are easy to miss. On this page you will learn how to ask for code that fits Paper 26.3, how to spot out-of-date code, how to check a result before it touches your server, and how to use an assistant to learn instead of just copy.
What an AI assistant is good and bad at
An AI assistant is a program that has read an enormous amount of text, including millions of lines of Minecraft plugin code, and writes new text that looks like what it read. It is useful and fast. It can explain a line, suggest a structure, write a first draft, and translate an error message into plain words.
It also has two weaknesses that matter a lot for plugins:
- It learned from old code. Most plugin code on the internet is years old. Minecraft and Paper change every year, so the assistant often writes the way plugins were written in 2019.
- It sounds sure when it is wrong. It can invent a method that does not exist and explain it like it does, and nothing in the answer warns you.
The good news: you can test almost everything, and this guide gave you the tools. The rest of this page is about doing that.
Tell it your versions every time
The single most useful habit is to start each new chat with a short block that describes your setup. Keep it in a text file and paste it first:
1I am a beginner writing a Paper plugin.2- Minecraft and Paper 26.3 (API io.papermc.paper:paper-api:26.3.build.142-beta)3- Java 254- Gradle with Kotlin DSL (build.gradle.kts), plugin.yml with api-version '26.3'5- Use the modern API only: Adventure Components or MiniMessage for text,6 Brigadier or BasicCommand for commands, no deprecated methods.7- No NMS or CraftBukkit imports unless I ask for them.8- Explain every new class or method you use in one short sentence.- The exact version name. Without it the assistant guesses, usually something like 1.16 or 1.20.
- Names the style you want. This one line removes many of the red flags listed below.
- NMS (internal server code) breaks often and is almost never needed. See Server internals and NMS.
- Turns each answer into a small lesson.
Good prompts
A prompt is the message you send. The more precise it is, the better the code. Compare these two:
1make a heal plugin1Write a /fullheal command for my Paper 26.3 plugin.2Behavior:3- Only players can use it. The console gets a red message.4- It needs the permission aifixed.fullheal.5- The player is healed to full health after 3 seconds, not right away.6- If the player left the server in those 3 seconds, do nothing.7Constraints: Java 25, no deprecated API, no waiting on the main thread.8Give me the files I need and tell me what goes in plugin.yml.- Describe what a player sees and what happens, not how to code it. This is the part only you know.
- The edge cases. Beginners forget them; naming one or two teaches the assistant to handle the rest.
- The server runs on one thread. Telling the assistant avoids
Thread.sleep, which would freeze everyone.
Here are three prompt shapes that cover most of what you will do. Pick the one that fits:
- Say what it does, as a list of behaviors.
- Say who can use it and what happens when they cannot.
- List the edge cases you can think of.
- Ask for the answer in small pieces: first the listener, then the command.
- Paste the exact error text, from the first line (
Caused by:) down to the first line that mentions your own class. - Paste the file the error points to. Add the line number if the error gives one.
- Say what you did right before it happened and what you expected instead.
- Remember to include your setup block.
The Error Doctor can often tell you the cause without any AI.
- Paste the code and say "I am a beginner".
- Ask for it line by line, with one sentence per line.
- Ask what would happen if you changed one specific thing, such as the number 60 to 6.
- Ask for the names of the Paper classes it uses, so you can look them up.
The Code Explainer does a first pass offline, and you can ask the assistant about whatever is still unclear.
Red flags in generated code
Here is what an assistant that has not been told your versions might write for the strong prompt above. It looks reasonable. Read it once, then look at the notes. A word you will see a lot is deprecated: the authors have marked a method as old, and it may be removed. Every example in this guide is compiled with deprecation warnings treated as errors, so you only see current code here.
1public class HealPlugin extends JavaPlugin implements Listener {2 3 public void onEnable() {4 getServer().getPluginManager().registerEvents(this, this);5 getCommand("fullheal").setExecutor(this);6 }7 8 @EventHandler9 public void onChat(AsyncPlayerChatEvent event) {10 if (event.getMessage().equalsIgnoreCase("help")) {11 event.getPlayer().sendMessage(ChatColor.GREEN + "Try /fullheal");12 }13 }14 15 public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {16 Player player = (Player) sender;17 player.sendMessage("Healing in 3 seconds...");18 try {19 Thread.sleep(3000);20 } catch (InterruptedException exception) {21 }22 player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue());23 return true;24 }25}- The old way to register a command. It needs a
commands:block inplugin.yml. In apaper-plugin.ymlplugin that block is not read, sogetCommandreturnsnulland the plugin crashes on startup. See the classic way. - The old chat event, deprecated. The modern one is
AsyncChatEvent, whose message is a Component, not a String. - Deprecated. Use MiniMessage tags or
NamedTextColor. See MiniMessage. - Crashes with a
ClassCastExceptionthe first time the console runs the command. - Freezes the whole server for 3 seconds, because commands run on the one main thread. Nobody can move or chat. A long enough freeze makes Paper's watchdog crash the server. Use the scheduler to wait.
- This name was removed when the attribute was renamed to
MAX_HEALTH. It will not compile on 26.3. - Can return
null, and the code uses the result without checking.
One answer, seven problems. Keep this table: it covers the most common red flags you will see in generated code.
| If you see... | Why it is a problem | Ask for this instead |
|---|---|---|
ChatColor, "&a" or "§a" color codes | Deprecated; the old text system. | MiniMessage (sendRichMessage("<green>...")) or Components |
AsyncPlayerChatEvent | Deprecated chat event with String messages. | io.papermc.paper.event.player.AsyncChatEvent |
getCommand("x") with a paper-plugin.yml | Returns null because that file has no commands: section. | Brigadier or BasicCommand with registerCommand |
Attribute.GENERIC_MAX_HEALTH, getMaxHealth() | Renamed or deprecated. | Attribute.MAX_HEALTH |
setDisplayName(String), setLore(List<String>) | Deprecated String versions. | displayName(Component), lore(List<Component>) |
Imports from net.minecraft or org.bukkit.craftbukkit | Internal code that changes with each version, needed very rarely. | Normal Paper API. Ask "is there a Paper API for this?" first. |
Thread.sleep, while (true) waiting | Freezes the single main thread. | The scheduler: runTaskLater |
| Web requests, database calls or big file reads in a normal event or command | They block the main thread and cause lag. | Do them with the async scheduler, then come back to the main thread. See Threads, performance and lag. |
| A method you cannot find in the Javadoc or autocomplete | The assistant made it up. | Ask for the class where it lives, then look it up. |
Here is the same plugin after checking it against the table. It is a real project that compiles on Paper 26.3. Compare it with the "bad" version line by line:
Where this file livesai-assistants-fixedsrcmainjavacomexampleaiassistantsfixedHelpChatListener.java
The package com.example.aiassistantsfixed is the folder path com/example/aiassistantsfixed 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.
- ai-assistants-fixed/
- src/main/
- java/com/example/aiassistantsfixed/Package com.example.aiassistantsfixed
- AiFixedPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- FullHealCommand.javaCommand (BasicCommand)
- HelpChatListener.javayou are hereListener: reacts to events
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/aiassistantsfixed/Package com.example.aiassistantsfixed
- 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.aiassistantsfixed;2 3import io.papermc.paper.event.player.AsyncChatEvent;4import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;5import org.bukkit.event.EventHandler;6import org.bukkit.event.Listener;7 8public final class HelpChatListener implements Listener {9 10 @EventHandler11 public void onChat(AsyncChatEvent event) {12 String message = PlainTextComponentSerializer.plainText().serialize(event.message());13 if (message.equalsIgnoreCase("help")) {14 event.getPlayer().sendRichMessage("<green>Try <yellow>/fullheal</yellow> to restore your health.");15 }16 }17}- The modern chat event.
- Chat messages are Components now. This turns one back into plain text so we can compare it with a word.
- True for
help,HELPandHelp. - MiniMessage replaces
ChatColor.
Where this file livesai-assistants-fixedsrcmainjavacomexampleaiassistantsfixedFullHealCommand.java
The package com.example.aiassistantsfixed is the folder path com/example/aiassistantsfixed 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.
- ai-assistants-fixed/
- src/main/
- java/com/example/aiassistantsfixed/Package com.example.aiassistantsfixed
- AiFixedPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- FullHealCommand.javayou are hereCommand (BasicCommand)
- HelpChatListener.javaListener: reacts to events
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/aiassistantsfixed/Package com.example.aiassistantsfixed
- 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.aiassistantsfixed;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import org.bukkit.attribute.Attribute;6import org.bukkit.attribute.AttributeInstance;7import org.bukkit.entity.Player;8import org.bukkit.plugin.Plugin;9 10public final class FullHealCommand implements BasicCommand {11 12 private static final long THREE_SECONDS_IN_TICKS = 60L;13 14 private final Plugin plugin;15 16 public FullHealCommand(Plugin plugin) {17 this.plugin = plugin;18 }19 20 @Override21 public void execute(CommandSourceStack source, String[] args) {22 if (!(source.getSender() instanceof Player player)) {23 source.getSender().sendRichMessage("<red>Only players can use this command.");24 return;25 }26 player.sendRichMessage("<gray>Healing in 3 seconds...");27 plugin.getServer().getScheduler().runTaskLater(plugin, () -> heal(player), THREE_SECONDS_IN_TICKS);28 }29 30 private void heal(Player player) {31 AttributeInstance maxHealth = player.getAttribute(Attribute.MAX_HEALTH);32 if (!player.isOnline() || maxHealth == null) {33 return;34 }35 player.setHealth(maxHealth.getValue());36 player.sendRichMessage("<green>You feel much better.");37 }38 39 @Override40 public String permission() {41 return "aifixed.fullheal";42 }43}- The modern simple-command type. No
commands:block inplugin.ymlis needed. - A named constant instead of a lonely
60. One second is 20 ticks. - Checks that the sender is a player, and gives it a name in one step. The console gets a friendly message instead of a crash.
- Asks the scheduler to run
healafter 60 ticks. The server keeps running normally in between. - The player may have left during the 3 seconds. Check before touching them.
- The attribute can be missing, so we check before using it.
- The current name.
- The permission needed, which Paper checks before running the command.
Where this file livesai-assistants-fixedsrcmainjavacomexampleaiassistantsfixedAiFixedPlugin.java
The package com.example.aiassistantsfixed is the folder path com/example/aiassistantsfixed 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.
- ai-assistants-fixed/
- src/main/
- java/com/example/aiassistantsfixed/Package com.example.aiassistantsfixed
- AiFixedPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- FullHealCommand.javaCommand (BasicCommand)
- HelpChatListener.javaListener: reacts to events
- resources/Files copied into the jar as they are
- plugin.ymlTells Paper the plugin's name, version and main class
- java/com/example/aiassistantsfixed/Package com.example.aiassistantsfixed
- 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.aiassistantsfixed;2 3import java.util.List;4import org.bukkit.plugin.java.JavaPlugin;5 6public final class AiFixedPlugin extends JavaPlugin {7 8 @Override9 public void onEnable() {10 getServer().getPluginManager().registerEvents(new HelpChatListener(), this);11 registerCommand("fullheal", "Restores your health after three seconds", List.of(), new FullHealCommand(this));12 }13}- Registers the chat listener.
- Registers the command directly in code, with a description and no aliases.
Try /fullheal to restore your health.
Healing in 3 seconds...
You feel much better.
Check AI code before you trust it
Use the same routine for every piece of generated code, even when it looks perfect:
Commit first
Before you paste new code into your project, commit what you have (see Git and GitHub). If the AI's change makes things worse, one click returns you to the working version.
Read it, top to bottom
You do not have to understand everything yet. You do need to find every class and method name you do not recognize. Those are the places where it may be invented or outdated.
Compile it
Paste the code into the Compile Lab, or into IntelliJ, and build. Red names and compile errors are very often the assistant using something that does not exist in 26.3. Paste the exact error back to the assistant.
Explain it to yourself
Put the code in the Code Explainer, or ask the assistant to explain every line. If an answer does not make sense, ask again. Never run code you cannot describe in your own words.
Run it and try to break it
Start your test server (Test Server) and use the feature the way a player would. Then use it the way a careless player would: from the console, with no arguments, with a name that does not exist, while another player watches. These are the cases AI skips.
Never paste secrets, and mind licenses
An AI chat is a website run by a company. Treat anything you type as something that could be stored or seen by someone else. So never paste:
- Passwords, API keys, tokens, Discord webhook addresses, database logins (see the secrets section). Replace them first with
FAKE_TOKEN. - The IP addresses and private details of your players, for example from log files.
- Code that belongs to someone else and is not yours to share, such as a paid plugin's source.
Licenses work the other way too. Code an assistant writes can still look a lot like code from a public project, and open source licenses (see Releasing and sharing your plugin) have rules. A few habits keep you safe: do not ask it to copy a named plugin; understand and rewrite what you use; and when you publish, you are responsible for everything in your jar.
The learning loop
You are here to learn to write plugins, not just to collect plugin code. Assistants can help with that, if you use them as a tutor and not a vending machine.
- Ask why. "Why is
runTaskLaterbetter thanThread.sleep?" A good answer makes you stronger, and a vague one tells you to check the docs. - Rewrite it yourself. After you understand the code, close the chat and type the feature again from memory in a new file. When you get stuck, look back at only the part you forgot.
- Ask for alternatives. "Show two other ways to do this and when I would choose each."
- Ask to be quizzed. "Ask me three questions about this code, one at a time, and wait for my answers."
- Change one thing at a time. Change the delay or the message and predict what will happen before you run it.
A useful rule of thumb: if you could not explain a piece of code to a friend, it is not yours yet. Keep going until it is.
Mistakes to avoid
- Pasting a whole project and saying "fix it". Send the one file and the one error.
- Skipping the versions. This single mistake causes most of the old code you will get.
- Trusting a method name because it sounds right. Look it up in the API map or autocomplete.
- Running generated code on your real server first. Always test locally.
- Letting the assistant write everything. You will end up with a plugin you cannot fix.
Spot the red flags
An assistant wrote this to give every new player a message. Find every problem, and say what you would ask for instead.
1@EventHandler2public void onJoin(PlayerJoinEvent event) {3 Player player = event.getPlayer();4 player.sendMessage("§aWelcome " + player.getName());5 player.setDisplayName("§6" + player.getName());6 Thread.sleep(2000);7 player.sendMessage("§7Read the rules with /rules");8}Hint 1
There are four problems. One of them stops the code from even compiling as written.
Hint 2
Look at the color codes, setDisplayName, and the line that waits.
Show the solution
§aand§6are old color codes. Use MiniMessage:sendRichMessage("<green>Welcome <name>", ...).setDisplayName(String)is deprecated. Useplayer.displayName(Component).Thread.sleep(2000)freezes the whole server for two seconds on every join. Use the scheduler (runTaskLater) to send the second message later.Thread.sleepthrows a checkedInterruptedException, so the code does not compile without atryandcatch. That is a hint that you are doing something wrong.
The fixed idea: send the first message at once, and let the scheduler send the second one 40 ticks later.
Write the prompt
You want a plugin where players who type /spawn teleport to the world's spawn point after a 5 second countdown, and the countdown stops if they move. Write the prompt you would send, including your setup, the behaviors and at least two edge cases.
Hint
Edge cases: the console runs it; the player moves during the countdown; the player runs the command twice.
Show the solution
1Setup: Paper 26.3, Java 25, Gradle Kotlin DSL, modern API only (Components, BasicCommand or Brigadier,2no deprecated methods, no NMS). Explain each new method in one sentence.3 4Write a /spawn command.5- Only players can use it. Others get a red message.6- When used, the player sees "Teleporting in 5..." and the number counts down each second.7- At 0 they teleport to their world's spawn location.8- If they move to a different block during the countdown, cancel it and tell them.9- If they run /spawn again while counting down, tell them it is already running.10- Never block the main thread.11Give me the files and the plugin.yml.It names the versions, lists behavior in a line each, includes the three edge cases, and sets constraints. When the answer arrives, run the five-step check above.
Recap
- Assistants write fast, but from old examples and with great confidence. Test everything.
- Start each chat with your versions: Paper 26.3, Java 25, Gradle Kotlin DSL, modern API.
- Good prompts describe behavior, edge cases and constraints, and include your current code and the exact error.
- Red flags:
ChatColor,AsyncPlayerChatEvent,getCommandwith a Paper plugin file,GENERIC_MAX_HEALTH, needless NMS,Thread.sleep, blocking work on the main thread. - Check: commit, read, compile, explain, run and try to break it.
- Never paste secrets or private data. Respect licenses.
- Ask why, then rewrite it yourself, so the knowledge becomes yours.
Quick quiz
Why does an AI assistant often write out-of-date plugin code?
It copies the style of what it read. Saying "Paper 26.3, Java 25, modern API" in your message steers it toward current code.Which line is a red flag on Paper 26.3?
ChatColoris the deprecated way to color text. MiniMessage and the scheduler are the current tools.Why is
Thread.sleep(3000)in a command handler a bad idea?Java allows it, but the server has only one main thread, and waiting on it stops everything. The scheduler waits without stopping the server.You want to ask for help with an error. What should you paste?
Exact text lets the assistant find the real cause. Never paste passwords, tokens or other secrets into a chat.The assistant writes a method you cannot find in autocomplete or the Javadoc. What do you do?
Invented method names are a classic AI error. If the compiler cannot find it, it is not in the API you compile against.
Next steps
- Where to go next: official docs, communities, and a ladder of project ideas.
- Compile Lab and Code Explainer: the two tools used in the check routine.
- Common errors: look up an error message before you ask anyone.