Minecraft plugin yazma işi, dışarıdan göründüğü kadar karmaşık değil: Java 25, bir Maven projesi, paper-api bağımlılığı ve JavaPlugin sınıfından türeyen tek bir ana sınıf ile ilk eklentinizi yarım saatte çalışır hâle getirebilirsiniz. Bu rehberde Paper API ile sıfırdan bir eklenti kuruyor, event dinliyor, komut kaydediyor, config.yml okuyor ve jar’ı sunucuya yükleyip test ediyoruz.
Rehber 2026’nın güncel kararlı sürümü olan Minecraft 26.2 “Chaos Cubed” üzerine yazıldı. 26.1 ile gelen unobfuscation değişikliği eklenti geliştirmenin en can sıkıcı kısmını (mapping ve remapping) ortadan kaldırdı, ama beraberinde eski eklentileri kıran bir ABI değişikliği getirdi. Ayrıntısını aşağıda ayrı bir bölümde ele alıyoruz.
Başlamadan Önce Gerekenler#
Geliştirme ortamı için üç şeye ihtiyacınız var: bir JDK, bir editör ve üzerinde deneme yapacağınız bir test sunucusu.
- JDK
- Eclipse Temurin JDK 25. Sadece JRE yetmez; derleme yapacağınız için JDK gerekir.
- Editör
- IntelliJ IDEA Community Edition ücretsizdir ve Maven desteği yerleşik gelir. Visual Studio Code + Java eklenti paketi de kullanılabilir.
- Derleme aracı
- Maven (bu rehberde) ya da Gradle. IntelliJ her ikisini de kendi içinde barındırır, ayrıca kurmanız gerekmez.
- Test sunucusu
- Paper 26.2. Canlı sunucunuzda geliştirme yapmayın.
Bağlantılar resmî kaynaklara gider. Paper API bağımlılığı Maven tarafından otomatik indirilir, ayrıca bir jar indirmenize gerek yoktur.
Hangi Minecraft sürümü için hangi Java’nın gerektiğini karıştırıyorsanız Minecraft sunucusu için hangi Java sürümü gerekir sayfasında tam tabloyu bulabilirsiniz. Test sunucusunu kurmak için Paper sunucu kurulumu rehberi yeterli.
Hangi API? Bukkit, Spigot ve Paper Farkı#
Eklenti ekosisteminde üç katmanlı bir API mirası var. Hepsi birbirinin üstüne kuruludur; Paper API en üstte yer alır ve alttakilerin tamamını içerir.
| API | Kapsam | Ne zaman seçilir? |
|---|---|---|
| Bukkit API | En temel katman; blok, oyuncu, event, komut | Yalnızca CraftBukkit üzerinde çalışması şartsa |
| Spigot API | Bukkit + Spigot’a özgü birkaç ekleme | Spigot’a özel bir API çağrısı gerekiyorsa |
| Paper API | Spigot + Adventure, geniş event seti, modern komut API’si | Yeni eklentilerin varsayılan tercihi |
| Velocity API | Proxy tarafı; sunucu ağı yönlendirme, oyuncu aktarma | Sunucular arası geçiş eklentisi yazılıyorsa |
Pratikte 2026’da yeni bir eklentiyi Paper API ile yazmamak için sebep yok. Paper API ile derlenmiş eklenti Paper ve Purpur üzerinde sorunsuz çalışır; bu ikisi de Türkiye’deki sunucuların ezici çoğunluğunda kullanılıyor. Sunucu yazılımı seçimini sunucu yazılımları karşılaştırması sayfasında ayrıntılı ele aldık.
26.1 Unobfuscation: Geliştirici Tarafında Ne Değişti?#
26.1 “Tiny Takeover” ile Mojang, obfuscated sunucu jar’ı yayınlamayı bıraktı. Sunucu kodu artık gerçek sınıf, metot ve alan adlarıyla dağıtılıyor. Bunun geliştirici tarafındaki üç somut sonucu var:
-
Mapping katmanı ortadan kalktı#
Paper dahili remapper’ı tamamen kaldırdı, Spigot da mapping desteğini düşürdü, Fabric ise Yarn’ı bırakıp Mojang Mappings’e geçti. “Mojang-mapped mı Spigot-mapped mı derlemeliyim” sorusu artık yok.
-
Eski eklentiler yeniden derlenmeden çalışmaz#
Bu bir ABI kırılmasıdır, basit bir sürüm atlaması değil. 1.21.11 ve öncesi için derlenmiş, sunucu iç sınıflarına (NMS) dokunan hiçbir eklenti 26.1 ve üzerinde çalışmaz. Sürüm numaralandırmasının nasıl değiştiğini yeni sürüm numaralandırması sayfasında anlattık.
-
Sadece API kullanan eklentiler büyük ölçüde güvende#
Yalnızca Bukkit/Paper API çağıran bir eklenti, çoğu durumda
paper-apisürümünü güncelleyip yeniden derlemekle 26.x’e taşınır. Bu yüzden NMS’ten mümkün olduğunca uzak durun.
paperweight-userdev eklentisi doğru bağımlılığı hazırlar.Maven Projesini Kurma#
IntelliJ IDEA’da New Project → Maven ile boş bir proje açın, ardından pom.xml dosyasını aşağıdaki gibi düzenleyin. Bu dosya, projenin adını, Java sürümünü ve Paper API bağımlılığını tanımlar.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.batihost</groupId>
<artifactId>IlkEklenti</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>26.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>25</release>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
Üç ayrıntı önemli. scope değeri provided olmalı: API sınıfları sunucu tarafından zaten sağlandığı için jar’ınızın içine paketlenmemeli. release değeri 25 olmalı, çünkü 26.x sunucuları Java 25 ister. filtering ise plugin.yml içinde ${project.version} yazabilmenizi sağlar; sürümü tek yerden yönetirsiniz.
| Hedef Minecraft | Java | paper-api sürümü |
|---|---|---|
| 26.2 (güncel kararlı) | Java 25 | 26.2-R0.1-SNAPSHOT |
| 26.1 / 26.1.1 / 26.1.2 | Java 25 | 26.1-R0.1-SNAPSHOT hattı |
| 1.20.5 - 1.21.11 | Java 21 | 1.21.x-R0.1-SNAPSHOT |
| 1.17 - 1.20.4 | Java 17 | 1.20.x-R0.1-SNAPSHOT |
plugin.yml Dosyası#
src/main/resources/plugin.yml dosyası olmadan jar’ınız eklenti sayılmaz; sunucu açılışta “plugin.yml bulunamadı” diyerek yükleme yapmaz. En temel hâli şudur:
name: IlkEklenti
version: '${project.version}'
main: com.batihost.ilkeklenti.IlkEklenti
api-version: '26.2'
author: Batihost
description: Batihost wiki icin ornek eklenti
commands:
merhaba:
description: Oyuncuya selam verir
usage: /merhaba
permission: ilkeklenti.merhaba
permissions:
ilkeklenti.merhaba:
description: Merhaba komutunu kullanma izni
default: true
main alanı, ana sınıfın tam paket yolu olmalıdır; tek bir harf hatası “Cannot find main class” hatası verir. api-version yazmazsanız sunucu eklentiyi eski kuşak sayar ve konsola uyarı basar.
Paper’ın kendi eklenti biçimi olan paper-plugin.yml de kullanılabilir; bootstrapper ve özel sınıf yükleyici gibi ileri seviye özellikler sunar. İlk eklentiniz için klasik plugin.yml yeterlidir. İzin isimlerini LuckPerms ile yetki yönetimi tarafında gruplara atayabilirsiniz.
Ana Sınıfı Yazma#
src/main/java/com/batihost/ilkeklenti/IlkEklenti.java dosyasını oluşturun. Ana sınıf JavaPlugin’den türer ve iki yaşam döngüsü metodu içerir.
package com.batihost.ilkeklenti;
import org.bukkit.plugin.java.JavaPlugin;
public final class IlkEklenti extends JavaPlugin {
@Override
public void onEnable() {
saveDefaultConfig();
getServer().getPluginManager().registerEvents(new GirisDinleyici(this), this);
getCommand("merhaba").setExecutor(new MerhabaKomutu());
getLogger().info("IlkEklenti etkinlestirildi.");
}
@Override
public void onDisable() {
getLogger().info("IlkEklenti kapatildi, kaynaklar birakildi.");
}
}
onEnable sunucu açılırken bir kez, onDisable kapanırken bir kez çalışır. Bu iki metot içinde ağır iş yapmayın: veritabanı bağlantısı, dosya okuma gibi işlemleri asenkron görevlere taşıyın, aksi hâlde sunucu açılışı uzar.
final yapmak ve constructor yazmamak iyi bir alışkanlıktır. Eklenti örneğini sunucu oluşturur; siz new IlkEklenti() çağırmazsınız.Event Dinleme#
Eklentilerin asıl gücü event sisteminden gelir. Oyuncu girdiğinde, blok kırıldığında, sohbet mesajı gönderildiğinde çalışan kod yazabilirsiniz. Aşağıdaki dinleyici, giriş yapan oyuncuya config.yml içinde tanımlı mesajı gönderir.
package com.batihost.ilkeklenti;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class GirisDinleyici implements Listener {
private final IlkEklenti plugin;
public GirisDinleyici(IlkEklenti plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
String mesaj = plugin.getConfig().getString(
"hosgeldin-mesaji", "Sunucuya hos geldiniz!");
event.getPlayer().sendMessage(
Component.text(mesaj, NamedTextColor.GREEN));
}
}
Dikkat edilecek noktalar: sınıf Listener arayüzünü uygulamalı, her dinleyici metodun başında @EventHandler anotasyonu bulunmalı ve sınıf onEnable içinde registerEvents ile kaydedilmelidir. Bu üç adımdan biri eksikse metot hiç çalışmaz ve hata da vermez; yeni başlayanların en çok vakit kaybettiği yer burasıdır.
Paper, metin göndermek için Adventure kütüphanesini kullanır. Eski ChatColor sınıfı yerine Component kullanmak hem daha okunaklı hem de renk kodu kaçış karakterleriyle uğraşmanızı önler.
Komut Ekleme#
plugin.yml içinde tanımladığınız komutu bir sınıfa bağlamanız gerekir. En yalın hâli:
package com.batihost.ilkeklenti;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class MerhabaKomutu implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender,
@NotNull Command command,
@NotNull String label,
@NotNull String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Bu komut sadece oyun icinde calisir.");
return true;
}
player.sendMessage(Component.text(
"Merhaba " + player.getName() + "!", NamedTextColor.AQUA));
return true;
}
}
onCommand metodunun true döndürmesi “komut işlendi” anlamına gelir. false döndürürseniz sunucu plugin.yml içindeki usage satırını oyuncuya gösterir; hatalı kullanım durumunda bu davranış işinize yarar.
Alt komutlu, argüman tipi doğrulaması yapan komutlar için Paper’ın Brigadier tabanlı modern komut API’si daha uygundur. Tamamlama önerilerini (tab complete) ve argüman tiplerini kendiniz yazmak zorunda kalmazsınız. Sunucu tarafındaki hazır komutların listesi için konsol komutları rehberine bakabilirsiniz.
config.yml ile Ayar Dosyası#
Kullanıcının değiştirebileceği her değeri koda gömmek yerine config.yml dosyasına taşıyın. src/main/resources/config.yml dosyasını oluşturun:
hosgeldin-mesaji: "Batihost sunucusuna hos geldiniz!"
duyuru-araligi-saniye: 300
duyurular:
- "Kurallari okumayi unutmayin."
- "Discord sunucumuza katilin."
onEnable içindeki saveDefaultConfig() çağrısı, dosya sunucuda yoksa jar’ın içinden kopyalar. Değerleri okumak için getConfig().getString(...), getInt(...), getStringList(...) metotlarını kullanırsınız. Kullanıcı dosyayı elle düzenlediyse reloadConfig() ile tazeleyebilirsiniz; bunun için genelde bir /eklentiadi reload alt komutu yazılır.
config.yml, eklentinin açılışta devre dışı kalmasına ve konsolda InvalidConfigurationException yığın izine yol açar. Yığın izini okumayı crash report okuma sayfasında anlattık.Derleme, Kurulum ve Test#
-
Projeyi derleyin#
Proje kök dizininde şu komutu çalıştırın:
mvn clean packageIntelliJ kullanıyorsanız sağdaki Maven panelinden Lifecycle → package adımına çift tıklamak aynı işi yapar.
-
Jar dosyasını bulun#
Derleme başarılıysa
target/IlkEklenti-1.0.0.jardosyası oluşur.original-önekli dosya varsa onu değil, öneksiz olanı kullanın. -
plugins klasörüne kopyalayın#
Jar’ı test sunucunuzun
pluginsklasörüne atın ve sunucuyu yeniden başlatın. Kurulum ayrıntıları için plugin kurulumu rehberi yeterli. -
Konsol çıktısını doğrulayın#
Açılış günlüğünde eklentinizin adını ve
onEnableiçinde yazdığınız satırı görmelisiniz. Oyuna girip/merhabakomutunu deneyin.
reload ya da reload confirm komutunu kullanmayın. Eski sınıf yükleyici bellekte kalır, dinleyiciler iki kez tetiklenir ve teşhisi neredeyse imkânsız hatalar ortaya çıkar. Her denemede sunucuyu tam olarak yeniden başlatın ve test dünyanızın yedeğini alın.Sık Yapılan Hatalar#
| Belirti | Neden | Çözüm |
|---|---|---|
| Cannot find main class | plugin.yml içindeki main yolu yanlış | Paket adı + sınıf adını birebir yazın |
| UnsupportedClassVersionError | Eklenti Java 25 ile derlendi, sunucu eski Java çalıştırıyor | Sunucuyu Java 25 ile başlatın |
| Event metodu hiç çalışmıyor | registerEvents çağrılmamış veya @EventHandler eksik | Üç adımı da kontrol edin |
| NullPointerException, getCommand satırında | Komut plugin.yml içinde tanımlı değil | Komutu commands bloğuna ekleyin |
| NoSuchMethodError / NoClassDefFoundError | Eski API sürümüne göre derlenmiş kod | paper-api sürümünü güncelleyip yeniden derleyin |
| Jar boyutu çok büyük | Bağımlılık provided yerine compile kapsamında | Paper API için kapsamı provided yapın |
Sonraki Adımlar ve Yayınlama#
Temel iskeleti kurduktan sonra ilerleyebileceğiniz doğal yönler şunlar:
- Zamanlanmış görevler:
BukkitSchedulerile belirli aralıklarla çalışan duyuru sistemi yazmak. - Veri saklama: Oyuncu verilerini YAML yerine SQLite veya MySQL’de tutmak; veritabanı çağrılarını mutlaka asenkron yapmak.
- Diğer eklentilerle konuşmak: Vault üzerinden ekonomi, PlaceholderAPI üzerinden değişken sağlamak.
- Performans: Ana thread’i bloklamamak. Yazdığınız kodun tick süresine etkisini spark profiler ile ölçün; ayrıntılar spark ve timings analizi sayfasında.
Eklentiniz çalışır hâle geldiğinde Hangar, Modrinth veya SpigotMC üzerinden ücretsiz yayınlayabilirsiniz. Yayın sayfasında desteklenen Minecraft sürüm aralığını net belirtin; 26.x geçişinden sonra kullanıcıların ilk sorduğu şey bu. Popüler eklentilerin nasıl konumlandığını görmek için 2026 eklenti listesine göz atın.
Geliştirme ve test için ayrı bir makine kullanmak isterseniz yüksek saat hızlı Ryzen tabanlı bir yüksek frekanslı VDS derleme sürelerini kısaltır; canlı yayına aldığınız sunucu için ise Minecraft sunucu paketlerine bakabilirsiniz. Eklenti geliştiren sunucularda 8 GB RAM alt sınırdır; birden fazla test dünyası ve ağır eklenti yükünde 16 GB rahat çalışır.
Özetle#
Minecraft eklentisi yazmak için gereken minimum set bellidir: Java 25 JDK, Maven projesi, io.papermc.paper:paper-api:26.2-R0.1-SNAPSHOT bağımlılığı, JavaPlugin’den türeyen bir ana sınıf ve plugin.yml. Event dinleyicisi kaydedin, komutu plugin.yml üzerinden bağlayın, ayarları config.yml dosyasına taşıyın, mvn clean package ile derleyip jar’ı plugins klasörüne atın.
26.1 sonrası dönemde tek altın kural şu: API’de karşılığı olan hiçbir işi NMS ile yapmayın. Unobfuscation mapping derdini bitirdi ama sunucu iç yapısına dokunan kodu her sürümde kırılmaya açık bıraktı. Sadece API kullanan bir eklenti, sürüm geçişlerinde bağımlılık numarasını değiştirip yeniden derlemekle kurtulur.