Spring Framework AOT yaklaşımıyla Native Image üretimini; ölçüm, reflection hint’leri, Hibernate ORM sınırları ve Spring AI araç şemaları üzerinden ele alan uygulamalı Java backend geliştirme rehberi.
Spring Framework AOT ile Native Image Soğuk Başlatma Mühendisliği
Java backend geliştirme için AOT hedefini ölçülebilir tanımlamak
Native Image çalışmasına başlamadan önce hedefi "daha hızlı" diye değil, örneğin ilk HTTP yanıtı 900 ms altı, tepe RSS 180 MiB altı diye yazın. Aynı container imajında JVM ve native ikilisini karşılaştırın; CPU frekansını ve veri tabanı bağımlılığını sabitlemeden yapılan ölçüm, derleme türünden çok ortam farkını ölçer. Yerel başlangıç süresini hyperfine, bellek tepesini GNU /usr/bin/time ile kaydedin.
# Uygulama 8080 portunda /actuator/health yayınlıyor varsayılır
hyperfine --warmup 0 --runs 15 'sh -c "./target/app & p=$!; until curl -fsS http://127.0.0.1:8080/actuator/health >/dev/null; do sleep 0.01; done; kill $p; wait $p 2>/dev/null"'
/usr/bin/time -f 'elapsed=%e sec max_rss=%M KiB' ./target/appBu karşılaştırmada JVM tarafını da gerçekçi başlatın: sınıf yükleme, JIT derlemesi ve Spring bean oluşturma maliyetleri ilk isteğe yansır. Native ikilide ise bu bean grafiğinin önemli bölümü build-time aşamasında çözümlenir; karşılığında çalışma anında keşfedilen sınıflar, reflection ve proxy üretimi kapalı dünyada ayrıca bildirilmelidir. Java eğitimi veya java programlama eğitimi kapsamında bu ayrımı sadece süre grafiğiyle değil, iki süreç için aynı health-check eşiğiyle göstermek gerekir.
Spring Boot eğitimi bağlamında AOT derleme hattını kurmak
Spring Boot Maven eklentisinin process-aot hedefi, bean tanımlarını analiz edip AOT kaynaklarını üretir; GraalVM Native Build Tools eklentisi ise bunları native executable’a dönüştürür. CI’da önce JVM testlerini, ardından native test profilini ayırın; aksi halde native derleme süresi her hızlı birim test döngüsüne eklenir.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<imageName>orders-api</imageName>
<buildArgs>
<buildArg>--enable-url-protocols=http,https</buildArg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
# Önce AOT üretilen kaynakları inceleyin, sonra ikiliyi üretin
./mvnw -DskipTests spring-boot:process-aot
find target/spring-aot -type f | head
./mvnw -Pnative native:compile
./target/orders-apiDerleme başarılı olsa bile uygulamanın işlevsel olduğu sonucu çıkmaz. Native profilinde Testcontainers ile PostgreSQL kaldırıp en az bir repository çağrısı, JSON serileştirmesi ve hata yanıtı testi çalıştırın. Bu, Spring Framework’ün otomatik ürettiği hint’lerin kapsamadığı uygulama sınıflarını, yalnızca endpoint ilk kez çağrıldığında ortaya çıkan hatalardan önce yakalar. Java kursu laboratuvarlarında sık yapılan hata, sadece /actuator/health test edip gerçek controller yolunu hiç yürütmemektir.
Reflection, Jackson ve Spring Framework RuntimeHints sınırı
AOT analizinin göremediği en yaygın yol, sınıf adının yapılandırmadan gelmesidir: Class.forName, eklenti JAR’ı taraması veya Jackson’ın polimorfik tür çözümlemesi. Native image bu sınıfları varsayılan olarak image’a almaz; JVM’de çalışan kod native ortamda ClassNotFoundException ya da reflection erişim hatası verir. Çözüm, kullanılacak üyeleri mümkün olan en dar kapsamla RuntimeHintsRegistrar üzerinden kaydetmektir.
public final class PaymentHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection().registerType(
PaymentCreated.class,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INTROSPECT_PUBLIC_METHODS
);
}
}
@Configuration(proxyBeanMethods = false)
@ImportRuntimeHints(PaymentHints.class)
class NativeHintsConfiguration { }
public record PaymentCreated(UUID id, BigDecimal amount) { }Geniş bir reflect-config.json ile tüm paketi kaydetmek kısa vadede hatayı gizler, fakat image boyutunu ve analiz süresini gereksiz büyütür; ayrıca erişime açılan üye yüzeyini denetlemeyi zorlaştırır. Önce native çalıştırmada -H:+ReportExceptionStackTraces ile eksik türü bulun, sonra yalnız constructor mı yoksa method introspection mı gerektiğini ayırın. Jackson için DTO’ları record ve somut tip olarak tutmak, açık uçlu @JsonTypeInfo(use = Id.CLASS) kullanımından daha öngörülebilir bir kayıt kümesi üretir.
Hibernate ORM ve Spring Data JPA ile native çalışma sınırları
Hibernate ORM, lazy association için proxy ve bytecode enhancement kullanabildiğinden native image’da özellikle dikkat ister. Spring Data JPA repository’leri ve yaygın entity metamodeli AOT tarafından çoğunlukla algılansa da, runtime’da paket tarayıp entity ekleyen modüler tasarımlar kapalı dünya varsayımını bozar. Entity listesini build-time’da sabitleyin, uygulamanın gerçekten kullandığı ilişki erişimlerini native entegrasyon testinde yürütün.
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<executions>
<execution>
<configuration>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>true</enableDirtyTracking>
</configuration>
<goals><goal>enhance</goal></goals>
</execution>
</executions>
</plugin>
@Transactional(readOnly = true)
public OrderView get(UUID id) {
return repository.findProjectedById(id)
.orElseThrow(() -> new NoSuchElementException(id.toString()));
}Buradaki incelik, bytecode enhancement’ın N+1 sorununu çözmemesidir. OrderView projection’ı sadece gereken kolonları seçerek entity proxy’sinin web katmanına sızmasını önler; ancak detay ekranı kalemleri istiyorsa sorguya açıkça join fetch veya @EntityGraph eklenmelidir. Native geçişten önce ve sonra aynı PostgreSQL veri setinde EXPLAIN (ANALYZE, BUFFERS) çıktısını saklayın: başlangıç kazancı için sorgu planını değiştirmek, çoğu zaman daha pahalı bir üretim gerilemesidir.
Spring AI, Spring MCP ve Model Context Protocol araçlarını kapalı dünyada tutmak
Spring AI kullanan bir servis, araç tanımlarını annotation taramasıyla veya uygulama konfigürasyonundan üretebilir; Spring MCP ve Model Context Protocol entegrasyonlarında ise araç adı, giriş şeması ve izin verilen işlem kümesi sürümleme konusu olmalıdır. Native image açısından kritik nokta, bir MCP sunucusunun çalışma anında sınıf adı alıp yeni tool yüklemesidir: AOT bu sınıfı göremez. Araçları derleme anındaki Spring bean’leri olarak tanımlayın; tenant başına dinamik JAR yükleme yerine sabit tool seti ve veri tabanı tabanlı yetkilendirme kullanın.
@Component
final class OrderTools {
@Tool(description = "Returns one order by UUID; never mutates data")
OrderSummary findOrder(UUID orderId) {
// repository çağrısı ve tenant doğrulaması burada yapılır
return new OrderSummary(orderId, "PAID");
}
}
// CI doğrulaması: araç şemasını bir dosyaya çıkarıp PR'da diff edin.
// Beklenen sözleşme: tool adı, input alanları, required alanlar ve açıklama.Araç şemasını CI’da JSON olarak dışarı alıp git diff --exit-code ile denetlemek, LLM istemcisinin beklediği alan adının fark edilmeden değişmesini engeller. Özellikle UUID alanını serileştirirken istemcinin string göndereceğini ve boş string’in dönüşüm hatası yaratacağını test edin. Bu konu java microservices ekiplerinde önemlidir: MCP tool sözleşmesi HTTP endpoint sözleşmesi kadar kararlı olmalı, fakat tool çağrısının model tarafından tetiklenmesi nedeniyle her çağrı için kullanıcı/tenant bağlamı server tarafında tekrar doğrulanmalıdır.
Java fullstack eğitimi için dağıtım ve geriye dönüş kuralı
Native ikiliyi Kubernetes’e verirken JVM için kopyalanmış bellek limitlerini kullanmayın. Önce kubectl top pod ve uygulama metriğiyle p95 RSS değerini ölçün; ardından limitin üstüne en az yeniden bağlanma, TLS el sıkışması ve eşzamanlı istek için pay bırakın. Readiness probe’u yalnız port açılmasına değil, kritik bağımlılıkların durumuna bağlayın; fakat her probe çağrısında veri tabanına ağır sorgu çalıştırmayın.
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 1
periodSeconds: 3
resources:
requests:
cpu: 100m
memory: 160Mi
limits:
memory: 256MiCanary aşamasında JVM ve native sürümü aynı trafik diliminde ayrı deployment olarak çalıştırın; Micrometer üzerinden http.server.requests p50/p95, 5xx oranı ve pod OOM sayısını karşılaştırın. Başlatma zamanı iyileşip p95 yükseliyorsa sorun çoğu kez native değil, daha düşük bellek limiti altında bağlantı havuzu veya JSON tamponlarının baskılanmasıdır. Java fullstack eğitimi açısından frontend’in ilk yükleme beklentisini de bu SLO’ya bağlayın: API hazır olmadan CDN’in başarılı dönmesi, kullanıcı için gerçek bir hazır olma durumu değildir.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Java Spring Boot ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
Spring Boot eğitimi için Native Image başlangıç süresi nasıl ölçülür?
Süreyi process start ile değil, uygulamanın gerçek readiness endpoint’inin ilk başarılı yanıtı arasında ölçün. 15 ayrı süreçte hyperfine çalıştırın, her turda önceki süreci öldürün ve p50/p95 kaydedin. JVM ile native kıyasını aynı container imaj tabanı, aynı CPU limiti ve aynı konfigürasyonla yapın.
Spring Data JPA ve Hibernate ORM Native Image içinde lazy loading çalışır mı?
Çalışabilir; fakat entity, proxy ve enhancement davranışı build-time’da görünür olmalıdır. Hibernate enhancement eklentisini kullanın, native Testcontainers testinde lazy ilişkiyi transaction içinde gerçekten okuyun ve web katmanına entity yerine projection veya DTO döndürün. Transaction dışındaki lazy erişim hatası native’e özgü değildir; native geçiş bunu daha görünür hale getirir.
Spring AI ve Spring MCP ile Model Context Protocol tool’ları native image’da nasıl güvenli tutulur?
Tool sınıflarını runtime class loading ile değil Spring bean olarak derleme anında kaydedin. Her tool için giriş JSON şemasını CI’da snapshot/diff ile izleyin; çağrı anında modelin verdiği tenant veya kullanıcı bilgisini güvenilir kabul etmeyin, kimliği HTTP/MCP oturumundan server tarafında çıkarın ve yetkiyi repository sorgusuna uygulayın.
Java kursu veya java programlama eğitimi projelerinde JVM yerine Native Image ne zaman seçilmeli?
Kısa ömürlü worker, sık ölçeklenen API veya sıkı bellek limiti olan servislerde önce ölçüm yaparak değerlendirin. Uzun yaşayan ve yüksek sürekli trafik alan bir JVM servisi JIT optimizasyonundan yararlanabilir; bu durumda native’in soğuk başlangıç kazancı toplam maliyeti haklı çıkarmayabilir. Kararı başlangıç p95, RSS, build süresi ve işlevsel native test kapsamını birlikte yazarak verin.
AI / LLM Discovery
Bu makale Opendart Akademi Java eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


