Bir Java uygulamasını ilk kez bir Linux sunucuya taşıdığınızda karşılaştığınız tablo çoğu zaman şudur: proje geliştiricinin makinesinde sorunsuz çalışıyordur ama sunucuda java: command not found hatası ya da UnsupportedClassVersionError gibi sürüm uyuşmazlıkları çıkar. Bunun sebebi karmaşık değildir; Linux dağıtımlarının depolarında birden fazla Java sürümü bulunur, hangi sürümün varsayılan olacağı ve JAVA_HOME değişkeninin nereyi gösterdiği doğru ayarlanmadığında uygulama ya hiç başlamaz ya da beklenmedik şekilde davranır. Bu rehber, bir Spring Boot API'sinden bir Minecraft sunucusuna, kurumsal bir Tomcat uygulamasından basit bir .jar aracına kadar her senaryoda işinize yarayacak sağlam bir kurulum temelini adım adım kurmayı amaçlıyor.
Aşağıda önce JDK ile JRE arasındaki farkı netleştirecek, sonra hem Debian/Ubuntu (apt) hem de RHEL/AlmaLinux/Rocky (dnf) tarafında OpenJDK kurulumunu göstereceğiz. Ardından işin can alıcı noktasına, yani aynı sunucuda birden çok Java sürümünü barındırıp update-alternatives ile aralarında geçiş yapmaya, JAVA_HOME değişkenini kalıcı biçimde tanımlamaya ve son olarak uygulamanızı systemd servisi olarak arka planda 7/24 çalıştırmaya geçeceğiz. Komutları kendi sunucunuzda birebir çalıştırabilecek şekilde gerçekçi tutuyoruz.
JDK, JRE ve OpenJDK Arasındaki Fark#
Kuruluma başlamadan önce üç kavramı ayırmak, ilerideki kafa karışıklığını baştan önler. JRE (Java Runtime Environment) yalnızca Java uygulamalarını çalıştırmak için gereken sanal makineyi ve çekirdek kütüphaneleri içerir. JDK (Java Development Kit) ise JRE'nin üstüne javac derleyicisi, jar paketleyici, jshell ve jstack gibi geliştirme ve teşhis araçlarını ekler. Bir sunucuda çoğu zaman derlenmiş bir .jar ya da .war dosyasını çalıştırdığınız için teorik olarak JRE yeterli görünebilir; ancak pratikte JDK kurmanız şiddetle tavsiye edilir. Çünkü canlı bir sunucuda thread dump almak, heap dökümü incelemek ya da bazen kaynaktan küçük bir düzeltme derlemek gerekir ve bu araçların hepsi JDK ile gelir.
OpenJDK, Java'nın açık kaynaklı ve ücretsiz referans uygulamasıdır. Eskiden Oracle JDK ile lisans farkları önemli bir tartışma konusuydu; bugün production sunucuları için OpenJDK dağıtımları (Adoptium/Temurin, Amazon Corretto, Red Hat build of OpenJDK, dağıtımların kendi paketleri) tamamen yeterli ve lisans açısından güvenlidir. Bu rehber boyunca dağıtımın kendi deposundan gelen OpenJDK paketlerini kullanacağız, çünkü bunlar güvenlik güncellemelerini paket yöneticisiyle otomatik alır.
Sürüm seçiminde temel kural şudur: mümkünse bir LTS (Long Term Support) sürümü seçin. LTS sürümler yıllarca güvenlik yaması alır ve production için en güvenli tercihtir. Aşağıdaki tablo, bu rehberin yazıldığı dönemde yaygın kullanılan sürümleri özetliyor.
| Sürüm | Tür | Tipik Kullanım |
|---|---|---|
| Java 8 | LTS (eski) | Eski kurumsal uygulamalar, legacy sistemler |
| Java 11 | LTS | Hâlâ yaygın, birçok framework destekliyor |
| Java 17 | LTS | Modern Spring Boot 3, güncel projeler için standart |
| Java 21 | LTS | En yeni LTS, yeni projeler için önerilir |
Uygulamanızın hangi sürümü gerektirdiğini bilmiyorsanız, projenin pom.xml (Maven) veya build.gradle (Gradle) dosyasındaki sourceCompatibility / java.version alanına bakın. Bu değer, sunucuya kuracağınız JDK sürümünün alt sınırını belirler.
Debian ve Ubuntu Üzerinde OpenJDK Kurulumu#
Debian tabanlı sistemlerde (Ubuntu, Debian, Linux Mint) paket yöneticisi apt'tir. Önce paket listenizi güncellemekle başlayın; bu adım atlanırsa depo dizininiz eski kalır ve paket bulunamayabilir.
sudo apt update
Belirli bir sürümü kurmak için openjdk-<sürüm>-jdk kalıbını kullanırsınız. Örneğin Java 17 kurmak için:
sudo apt install -y openjdk-17-jdk
Yalnızca çalıştırma ortamına ihtiyacınız varsa openjdk-17-jre paketini seçebilirsiniz, ancak yukarıda açıkladığımız gerekçelerle sunucuda tam JDK kurmanızı öneririz. Kurulumun başarılı olduğunu doğrulamak için sürümü sorgulayın:
java -version
javac -version
Beklenen çıktı şuna benzer olmalıdır:
openjdk version "17.0.11" 2024-04-16
OpenJDK Runtime Environment (build 17.0.11+9-Ubuntu-1)
OpenJDK 64-Bit Server VM (build 17.0.11+9-Ubuntu-1, mixed mode, sharing)
Deponuzda hangi Java sürümlerinin bulunduğunu merak ediyorsanız arama yapabilirsiniz:
apt search openjdk | grep -E 'openjdk-[0-9]+-jdk'
Debian'ın kararlı deposu bazen en yeni Java sürümünü içermez. Java 21 veya daha yenisine ihtiyaç duyup depoda bulamazsanız, Eclipse Temurin (Adoptium) gibi bir dağıtımın APT deposunu ekleyebilir ya da doğrudan resmi tarball'ı /opt altına açıp bu rehberdeki update-alternatives yöntemiyle sisteme tanıtabilirsiniz. Temel Linux komutlarında rahat değilseniz, önce Linux temel komutları rehberine göz atmanız işinizi kolaylaştırır.
RHEL, AlmaLinux ve Rocky Linux Üzerinde Kurulum#
Red Hat ailesindeki dağıtımlarda (RHEL, AlmaLinux, Rocky Linux, Fedora, CentOS Stream) paket yöneticisi dnf'tir (eski sistemlerde yum). Paket adlandırması apt'ten biraz farklıdır; sürüm numarası paket adının içindedir ve genellikle "headless" bir varyant da sunulur.
sudo dnf install -y java-17-openjdk-devel
Buradaki -devel eki, javac dahil geliştirme araçlarını getirir; yani bu paket JDK karşılığıdır. Eğer sunucuda grafik arayüz yoksa (ki bir web sunucusunda genellikle yoktur) java-17-openjdk-headless paketi daha az bağımlılık çeker, fakat javac için yine -devel gerekir. Depoda mevcut sürümleri listelemek için:
dnf search openjdk
Kurulum sonrası doğrulama Debian tarafıyla aynıdır:
java -version
RHEL ailesinin güzel bir tarafı, aynı anda birden çok java-*-openjdk paketini yan yana kurabilmenize izin vermesidir. Örneğin hem Java 11 hem Java 17'yi kurup, hangisinin aktif olacağını alternatives mekanizmasıyla seçebilirsiniz. Bir sonraki bölümde tam olarak bunu ele alıyoruz.
Birden Çok Sürümü update-alternatives ile Yönetmek#
Gerçek dünyada tek bir sunucuda birden fazla uygulama, birbirinden farklı Java sürümleri talep edebilir. Eski bir raporlama servisi Java 8 isterken, yeni bir mikroservis Java 21 bekleyebilir. Linux'un update-alternatives (RHEL'de sadece alternatives) sistemi tam da bu ihtiyaç için vardır: aynı komuta (java, javac) karşılık gelen birden çok kurulumu yönetir ve /etc/alternatives altındaki sembolik bağlar üzerinden hangisinin varsayılan olacağını belirlemenizi sağlar.
Önce ikinci bir sürüm kuralım. Ubuntu'da Java 11'i mevcut Java 17'nin yanına ekleyelim:
sudo apt install -y openjdk-11-jdk
Şimdi sistemde kayıtlı java alternatiflerini interaktif olarak görüntüleyip seçmek için:
sudo update-alternatives --config java
Karşınıza şuna benzer bir menü çıkar:
Selection Path Priority Status
------------------------------------------------------------
* 0 /usr/lib/jvm/java-17-openjdk-amd64/bin/java 1711 auto mode
1 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1111 manual mode
2 /usr/lib/jvm/java-17-openjdk-amd64/bin/java 1711 manual mode
Press <enter> to keep the current choice[*], or type selection number:
İstediğiniz sürümün numarasını yazıp Enter'a bastığınızda varsayılan java o kuruluma sabitlenir. Çok önemli bir ayrıntı: java ve javac ayrı alternatiflerdir. Yani derleyiciyi de aynı sürüme almak için ikinci komutu da çalıştırmanız gerekir; aksi halde java 17'yi gösterirken javac 11'de kalabilir ve bu sinsi hatalara yol açar.
sudo update-alternatives --config javac
Kayıtlı tüm alternatifleri komut satırında hızlıca görmek için --list kullanabilirsiniz:
update-alternatives --list java
Manuel olarak /opt altına açtığınız bir tarball kurulumunu sisteme tanıtmak isterseniz, alternatifi elle ekleyebilirsiniz. Örneğin Temurin JDK 21'i /opt/jdk-21 altına açtığınızı varsayalım:
sudo update-alternatives --install /usr/bin/java java /opt/jdk-21/bin/java 2100
sudo update-alternatives --install /usr/bin/javac javac /opt/jdk-21/bin/javac 2100
Sondaki sayı (2100) önceliktir; auto mode'da en yüksek öncelikli alternatif otomatik seçilir. Bu yöntemle dağıtım deposunda olmayan sürümleri bile düzenli bir şekilde yönetebilirsiniz.
JAVA_HOME Ortam Değişkenini Doğru Ayarlamak#
java -version çalışıyor olsa bile birçok araç (Maven, Gradle, Tomcat, Elasticsearch, Jenkins) sürümü doğrudan java komutundan değil, JAVA_HOME ortam değişkeninden okur. Bu değişken yanlış ya da eksikse, uygulamanız komut satırında çalışırken servis olarak ayağa kalkmayabilir. JAVA_HOME, JDK'nin kurulu olduğu kök dizini göstermelidir; bin alt klasörünü değil.
Aktif java kurulumunun gerçek yolunu bulmak için sembolik bağları takip edin:
readlink -f $(which java)
Bu komut size /usr/lib/jvm/java-17-openjdk-amd64/bin/java gibi bir çıktı verir. JAVA_HOME için bin/java kısmını atmanız, yani /usr/lib/jvm/java-17-openjdk-amd64 yolunu kullanmanız gerekir.
Değişkeni tüm kullanıcılar için kalıcı yapmak istiyorsanız /etc/profile.d altında yeni bir betik oluşturmak en temiz yöntemdir. Bu dosyayı bir kez kurup ileride sürüm değiştirdiğinizde yalnızca yolu güncellemeniz yeterli olur:
sudo tee /etc/profile.d/java.sh > /dev/null <<'EOF'
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
EOF
Değişikliğin mevcut oturumda etkin olması için betiği yeniden yükleyin ve doğrulayın:
source /etc/profile.d/java.sh
echo $JAVA_HOME
Yalnızca kendi kullanıcınız için tanımlamak yeterliyse ~/.bashrc dosyasının sonuna aynı iki export satırını ekleyebilirsiniz. Kurumsal ortamlarda tek bir kullanıcının değil, sistem genelinin aynı JAVA_HOME'u görmesi genellikle tercih edilir; bu yüzden /etc/profile.d yaklaşımını öneriyoruz. Ortam değişkenleri ve kabuk yapılandırması konusunda derinleşmek isterseniz Linux temel komutları rehberi iyi bir başlangıçtır.
Bir Java Uygulamasını (jar) Çalıştırmak#
JDK kurulu ve JAVA_HOME ayarlı olduğuna göre artık bir uygulama çalıştırabiliriz. Java uygulamaları çoğunlukla tek bir çalıştırılabilir .jar dosyası olarak dağıtılır (özellikle Spring Boot "fat jar" üretir). Bir jar'ı çalıştırmanın en temel biçimi şudur:
java -jar uygulama.jar
Uygulamanıza harici parametre geçmeniz gerekiyorsa iki farklı yer vardır ve bu ayrım çok önemlidir. JVM'in kendisine ait ayarlar (bellek limiti, sistem özellikleri) -jar'dan önce; uygulamanın kendi argümanları ise -jar uygulama.jar'dan sonra yazılır:
java -Xms256m -Xmx1024m -Dserver.port=9090 -jar uygulama.jar --spring.profiles.active=prod
Burada -Xms256m başlangıç heap boyutunu, -Xmx1024m maksimum heap boyutunu belirler; -Dserver.port=9090 bir JVM sistem özelliğidir; --spring.profiles.active=prod ise Spring Boot'a geçen bir uygulama argümanıdır. Bellek limitlerini sunucunuzun RAM'ine göre ayarlamak önemlidir: -Xmx değerini sunucudaki toplam belleğin tamamına yakın vermeyin, işletim sistemi ve diğer servisler için pay bırakın.
Bir jar'ın içinde hangi ana sınıfın çalışacağını merak ederseniz manifest dosyasına bakabilirsiniz:
unzip -p uygulama.jar META-INF/MANIFEST.MF
Bu noktada uygulamanız çalışır ama SSH oturumunuzu kapattığınız anda süreç de sonlanır. nohup java -jar uygulama.jar & gibi geçici çözümler test için uygundur, ancak production'da asla yeterli değildir: sunucu yeniden başladığında uygulama gelmez, çöktüğünde kendini toparlamaz ve logları düzgün toplayamazsınız. Kalıcı ve profesyonel çözüm bir sonraki bölümde: uygulamayı bir systemd servisi hâline getirmek.
systemd Servisi Olarak Arka Planda Çalıştırmak#
Production'da bir Java uygulamasını yönetmenin doğru yolu onu systemd servisi yapmaktır. Böylece uygulama sunucu açılışında otomatik başlar, çöktüğünde yeniden ayağa kaldırılır ve tüm çıktısı journald üzerinden merkezi log sistemine akar. Önce uygulamayı kendi ayrıcalıksız kullanıcısıyla çalıştırmak güvenlik açısından iyi bir alışkanlıktır; hiçbir uygulamayı gereksiz yere root ile çalıştırmayın.
Adım adım kuralım. Önce özel bir kullanıcı ve dizin oluşturun, jar'ı yerleştirin:
sudo useradd -r -s /usr/sbin/nologin javaapp
sudo mkdir -p /opt/javaapp
sudo cp uygulama.jar /opt/javaapp/
sudo chown -R javaapp:javaapp /opt/javaapp
Ardından servis birim dosyasını /etc/systemd/system/javaapp.service olarak oluşturun:
[Unit]
Description=Clou.TR Java Uygulamasi
After=network.target
[Service]
Type=simple
User=javaapp
Group=javaapp
WorkingDirectory=/opt/javaapp
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java -Xmx1024m -jar /opt/javaapp/uygulama.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Birkaç ayrıntı önemlidir. ExecStart içinde java komutunun tam yolunu vermek en güvenlisidir, çünkü servis ortamı sizin kabuğunuzun PATH'ini miras almaz. SuccessExitStatus=143 satırı, JVM'in SIGTERM ile kapatıldığında döndürdüğü 143 çıkış kodunu "başarılı" sayarak servisin gereksiz yere hatalı görünmesini engeller. Restart=on-failure ise uygulama beklenmedik şekilde çökerse systemd'nin onu 10 saniye içinde yeniden başlatmasını sağlar.
Dosyayı kaydettikten sonra servisi tanıtın, başlatın ve açılışa ekleyin:
sudo systemctl daemon-reload
sudo systemctl enable --now javaapp
sudo systemctl status javaapp
Uygulamanın anlık loglarını takip etmek için:
sudo journalctl -u javaapp -f
Bu yapı, tek bir jar'dan kurumsal bir mikroservise kadar her ölçekte güvenilir çalışır. systemd birim dosyalarının detaylarını, bağımlılık sıralamasını ve gelişmiş yeniden başlatma politikalarını systemd servis yönetimi rehberinde daha derinlemesine bulabilirsiniz. Java uygulamanızın önüne bir ters proxy (Nginx) koyup HTTPS sonlandırması yapmak isterseniz, bir SSL sertifikası ile trafiği şifrelemek de bu aşamada mantıklı bir adımdır.
Sık Karşılaşılan Sorunlar ve Çözümleri#
Kurulum ne kadar dikkatli yapılırsa yapılsın birkaç klasik hata hemen herkesin başına gelir. En sık görüleni UnsupportedClassVersionError mesajıdır. Bu hata, uygulamanın sizin sunucudaki Java sürümünüzden daha yeni bir sürümle derlendiğini söyler; örneğin Java 21 ile derlenmiş bir jar'ı Java 17 üzerinde çalıştırmaya çalışırsanız bununla karşılaşırsınız. Çözüm, ya sunucuya gereken sürümü kurmak ya da uygulamayı hedef sürüme göre yeniden derlemektir.
İkinci klasik sorun, JAVA_HOME'un servis ortamında görünmemesidir. Komut satırında her şey çalışırken systemd servisi "java bulunamadı" diye başarısız oluyorsa, bunun sebebi neredeyse her zaman servisin sizin kabuk profilinizi okumamasıdır. Yukarıdaki birim dosyasında olduğu gibi Environment=JAVA_HOME=... satırını ve ExecStart'ta tam yolu açıkça belirtmek bu sorunu kökten çözer. Üçüncü olarak, bellek darboğazları vardır: küçük bir VPS'te -Xmx değerini fazla yüksek verirseniz uygulama OutOfMemoryError ile ya da OOM-killer tarafından öldürülerek çöker. Sunucunuzun kaynak planlamasını uygulamanın gerçek ihtiyacına göre yapmak, bu yüzden kritik önemdedir.
Son bir ipucu: alternatives ile varsayılan sürümü değiştirdiğinizde çalışan servisleri yeniden başlatmayı unutmayın. Zaten ayakta olan bir JVM, kendisi başlatıldığı sürümle çalışmaya devam eder; yeni sürümü ancak süreç yeniden başlatıldığında görür.
Sıkça Sorulan Sorular#
JDK mi JRE mi kurmalıyım?#
Sunucuda çoğunlukla hazır bir .jar çalıştıracak olsanız bile JDK kurmanızı öneririz. JDK, JRE'nin sağladığı çalıştırma ortamına ek olarak thread dump alma, heap inceleme ve gerektiğinde kaynaktan derleme araçlarını da içerir. Bu teşhis araçları bir production sorununda paha biçilmez olur ve JDK'nin ek disk maliyeti neredeyse önemsizdir.
java -version çalışıyor ama javac bulunamıyor, sorun ne?#
Bu durum genellikle sistemde tam JDK yerine yalnızca JRE'nin kurulu olduğunu ya da update-alternatives üzerinde java ile javac'in farklı sürümlere işaret ettiğini gösterir. Debian tarafında openjdk-<sürüm>-jdk, RHEL tarafında ise java-<sürüm>-openjdk-devel paketini kurduğunuzdan emin olun. Ardından sudo update-alternatives --config javac komutuyla derleyiciyi de doğru sürüme sabitleyin.
JAVA_HOME neyi göstermeli, bin/java yolunu mu?#
JAVA_HOME, JDK'nin kurulu olduğu kök dizini göstermelidir; yani bin/java'yı değil, onun bir üstündeki klasörü. Örneğin doğru değer /usr/lib/jvm/java-17-openjdk-amd64 iken, /usr/lib/jvm/java-17-openjdk-amd64/bin/java yanlıştır. Aktif yolu bulmak için readlink -f $(which java) komutunu çalıştırıp sondaki bin/java kısmını çıkarabilirsiniz.
Aynı sunucuda birden fazla Java sürümü tutabilir miyim?#
Evet, bu tamamen desteklenen bir senaryodur. Farklı sürümleri yan yana kurup update-alternatives (RHEL'de alternatives) ile aralarında geçiş yapabilirsiniz. Hatta sistem varsayılanını değiştirmeden, belirli bir uygulamanın systemd birim dosyasında ExecStart yoluyla ona özel bir Java sürümünü sabitleyebilirsiniz; böylece her uygulama kendi gereksinim duyduğu sürümle çalışır.
Uygulamayı nohup yerine neden systemd ile çalıştırmalıyım?#
nohup ... & yalnızca hızlı bir test için idare eder; production için yetersizdir. systemd ile uygulamanız sunucu yeniden başladığında otomatik ayağa kalkar, çöktüğünde yeniden başlatılır, ayrıcalıksız bir kullanıcıyla güvenli çalışır ve tüm logları journalctl üzerinden merkezi biçimde toplanır. Bu yetenekler ciddi bir servis için pazarlık konusu olmayan gereksinimlerdir.
Hangi Java sürümünü seçmeliyim?#
Yeni bir proje başlatıyorsanız güncel bir LTS sürümü (bu rehberin yazıldığı dönemde Java 21) tercih edin; LTS sürümler uzun süre güvenlik güncellemesi alır. Mevcut bir uygulamayı taşıyorsanız, projenin pom.xml veya build.gradle dosyasındaki Java sürüm ayarına bakıp en az o sürümü kurun. Emin değilseniz, uygulamanın derlendiği sürümle aynısını kullanmak en güvenli yoldur.
Kapanış#
Java'yı Linux'a kurmak, ilk bakışta tek bir apt install komutundan ibaret görünse de işin kalitesi ayrıntılarda saklıdır: doğru sürümü seçmek, update-alternatives ile sürümleri düzenli yönetmek, JAVA_HOME'u kalıcı ve tutarlı tanımlamak ve uygulamayı systemd ile güvenli biçimde arka planda çalıştırmak. Bu dört adımı bir kez doğru kurduğunuzda, ister bir mikroservis ister kurumsal bir uygulama olsun, sunucunuz Java iş yükleri için sağlam bir temele kavuşur.
Bu altyapıyı üzerine kuracağınız güçlü bir sunucu arıyorsanız Clou.TR yanınızda. Java uygulamalarınız için tam kök erişimli VDS sunucularımıza veya esnek sanal sunucu seçeneklerimize göz atabilir, kurulum ve bakımı bize bırakmak isterseniz sunucu yönetimi hizmetimizden faydalanabilirsiniz. Uygulamanızı yayına alırken trafiği şifrelemek için SSL sertifikası, saldırılara karşı DDoS koruma ve düzenli yedekleme çözümlerimizle projenizi baştan sona güvenceye alın. Sorularınız için destek ekibimiz her zaman bir tık uzağınızda.