← Back to list

DTO Nedir?

Data Transfer Object

Eda Kaş · 2026-05-31 10:19 · 12 claps · 5.1 min read paywalled
#java #dto #data-transfer-object #software-development #api-design
Open on Medium ↗

DTO Nedir?

Backend geliştirirken veritabanındaki entity sınıfımız ile API’den döndürmek istediğimiz veri aynı olmayabilir. Ya da frontend’den gelen request içinde sadece belirli alanları almak isteyebiliriz.

DTO kullanmaya başladığım ilk dönemlerde, her entity için ek bir class yazmak gereksiz bir maliyet gibi görünüyordu. Ancak projede endpoint sayısı arttıkça, entity’leri doğrudan dışarı açmanın uzun vadede iyi bir fikir olmadığını fark ettim. Özellikle kullanıcı bilgileri, token işlemleri, validation kuralları ve farklı response ihtiyaçları ortaya çıktığında, DTO kullanmanın kodu yönetmeyi ciddi anlamda kolaylaştırdığını gördüm.

DTO Nedir?

DTO, “Data Transfer Object” kelimelerinin kısaltmasıdır.

En basit haliyle DTO, katmanlar arasında veri taşımak için kullanılan nesnedir. Controller, service ve client arasındaki veri alışverişinde sıkça kullanılır.

DTO’nun amacı iş mantığı yazmak değildir. Sadece taşınacak verinin şeklini belirler.

Örneğin şu anda üzerinde çalıştığım projede kullanıcı bilgilerini frontend’e dönerken direkt User entity’sini dönmüyorum. Bunun yerine CurrentUserResponse adında bir DTO kullanıyorum.

public record CurrentUserResponse(
 Long id,
 String email,
 String name,
 String pictureUrl,
 String careerGoal,
 String location,
 UserRole role,
 LocalDateTime createdAt,
 LocalDateTime lastLoginAt
) {
}

Burada frontend’in ihtiyaç duyduğu alanları seçip response olarak dönüyorum. Entity içinde başka alanlar olabilir ama API response’unun bunları bilmesine gerek yok.

Neden Entity’yi Direkt Dönmüyoruz?

Entity sınıfları veritabanı modelimizi temsil eder. Şu anda üzerinde çalıştığım projede User entity’si içinde googleId, createdAt, updatedAt, role, lastLoginAt gibi alanlar var.

Frontend’in her zaman tüm bu alanlara ihtiyacı olmayabilir. Hatta bazı alanları API response içinde göndermek güvenli veya doğru olmayabilir.

Entity ile API response modelini ayırınca kodu değiştirmek daha rahat oluyor. Veritabanı tarafında bir alan eklediğimde, bunu otomatik olarak dış dünyaya açmış olmuyorum.

Service içinde entity’den DTO’ya dönüşüm yaptığım yer şöyle:

private CurrentUserResponse toCurrentUserResponse(User user) {
 return new CurrentUserResponse(
 user.getId(),
 user.getEmail(),
 user.getName(),
 user.getPictureUrl(),
 user.getCareerGoal(),
 user.getLocation(),
 user.getRole(),
 user.getCreatedAt(),
 user.getLastLoginAt()
 );
}

Burada User entity’sinden gelen verileri CurrentUserResponse DTO’suna çeviriyorum.

Request DTO Örneği

DTO sadece response için kullanılmaz. Request tarafında da kullanılır.

Mesela Google login işleminde frontend’den sadece idToken bekliyorum.

public record GoogleLoginRequest(
 @NotBlank(message = "Google ID token is required")
 String idToken
) {
}

Controller tarafında ise bu request DTO’sunu şöyle kullanıyorum:

@PostMapping("/google")
public GoogleLoginResponse loginWithGoogle(@Valid @RequestBody GoogleLoginRequest request) {
 return authService.loginWithGoogle(request.idToken());
}

Validation işlemini DTO üzerinde tanımlayabiliyorum. idToken boş gelirse Spring validation bunu yakalayabiliyor.

Burada record kullandığım için alana request.idToken() şeklinde erişiyorum. Normal class yapısında alışık olduğumuz getIdToken() metodu yerine record kendi accessor metodunu oluşturuyor.

Response DTO Örneği

Login başarılı olduğunda frontend’e access token ve kullanıcı bilgisini dönüyorum.

public record GoogleLoginResponse(
 String accessToken,
 String tokenType,
 CurrentUserResponse user
) {
}

Benim deneyimimde response DTO’larını bu şekilde parçalara ayırmak kodu daha okunabilir yapıyor. Özellikle auth gibi bir alanda hem token hem user bilgisi dönmek gerekiyorsa, her şeyi tek bir karmaşık yapıya doldurmak yerine DTO’ları anlamlı şekilde ayırmak daha temiz oluyor.

Başka Bir Örnek: Location DTO’ları

Şu anda üzerinde çalıştığım projede ülke ve şehir bilgilerini dönerken de DTO kullanıyorum.

public record CountryResponse(
 Long id,
 String code,
 String name
) {
}
public record CityResponse(
 Long id,
 String name
) {
}

Service içinde entity’leri DTO’ya çeviriyorum:

public List<CountryResponse> getCountries() {
 return countryRepository.findAllByOrderByNameAsc()
 .stream()
 .map(country -> new CountryResponse(
 country.getId(),
 country.getCode(),
 country.getName()
 ))
 .toList();
}

Burada database’den gelen Country entity’sini direkt dönmek yerine CountryResponse DTO’suna çeviriyorum.

Bu küçük bir örnek gibi görünebilir ama API response formatını kontrol altında tutmak açısından önemli. Bugün sadece id, code, name dönmek istiyorsam DTO bunu net şekilde ifade ediyor.

Java’da DTO İçin Record Kullanımı

Java’da DTO yazarken record kullanmak oldukça pratik.

Java 16 ve sonrasında record kullanmak DTO yazmayı daha hızlı ve daha temiz bir hale getiriyor. Projenizde Java 16+ kullanıyorsanız record’ları tercih edebilirsiniz. Ben şu anda Java 21 kullandığım için DTO tarafında record kullanmak benim için bir tercih oldu.

Normalde bir DTO class yazdığımızda constructor, getter, equals, hashCode, toString gibi metotlarla uğraşmamız gerekir. record bunları bizim için otomatik oluşturur.

Bu yüzden immutable yani sonradan değiştirilmeyen veri taşıma nesneleri için çok uygundur.

Örneğin:

public record UpdateUserProfileRequest(
 String careerGoal,
 String location
) {
}

Bu DTO ile kullanıcıdan sadece güncellenebilir profil alanlarını alıyorum.

Projede validation eklenmiş hali şöyle:

public record UpdateUserProfileRequest(
 @Size(max = 120, message = "Career goal must be at most 120 characters")
 String careerGoal,
@Size(max = 120, message = "Location must be at most 120 characters")
 String location
) {
}

Bence record burada çok okunabilir bir yapı sağlıyor. Sınıfın amacı zaten veri taşımak. Record da tam olarak bunu sade bir şekilde ifade ediyor.

Record Kullanmadan DTO Yazmak

Aynı DTO’yu record kullanmadan immutable bir class olarak yazmak istersek şöyle olurdu:

public class UpdateUserProfileRequest {
private final String careerGoal;
 private final String location;
public UpdateUserProfileRequest(String careerGoal, String location) {
 this.careerGoal = careerGoal;
 this.location = location;
 }
public String getCareerGoal() {
 return careerGoal;
 }
public String getLocation() {
 return location;
 }
}

Gördüğümüz gibi aynı işi yapmak için daha fazla kod yazmamız gerekiyor.

Burada setter eklemedim çünkü DTO’yu immutable tutmak istiyorum. DTO’nun oluşturulduktan sonra değişmemesi çoğu request/response senaryosunda daha güvenli bir yaklaşım.

Tabii class DTO kullanırken dikkat edilmesi gereken bazı detaylar var. Spring @ RequestBody ile class DTO kullanıyorsanız Jackson’ın nesneyi nasıl oluşturacağını düşünmeniz gerekir. No-args constructor ve setter yaklaşımı kullanılabilir.

Record kullandığımızda modern Spring Boot ve Jackson sürümlerinde bu kullanım oldukça sade hale geliyor.

DTO Kullanırken Fark Ettiğim Avantajlar

DTO kullanmanın en önemli tarafı entity ile API modelini birbirinden ayırması.

Benim deneyimimde bu ayrım özellikle proje büyümeye başladığında değerli hale geliyor. Başta küçük bir endpoint için DTO yazmak fazla gibi görünebilir. Ama sonrasında frontend’in istediği response değiştiğinde, entity’ye dokunmadan sadece DTO tarafında düzenleme yapabilmek büyük rahatlık.

DTO kullandığımızda entity içindeki her alanı dış dünyaya açmak zorunda kalmıyoruz. Frontend’e sadece ihtiyacı olan veriyi dönüyoruz. Request tarafında da hangi alanları kabul ettiğimizi daha net ifade ediyoruz.

Bir diğer avantaj da validation tarafı. Mesela GoogleLoginRequest içinde idToken için @ NotBlank kullanabiliyorum. UpdateUserProfileRequest içinde careerGoal ve location için maksimum karakter sınırı koyabiliyorum.

Yani DTO sadece “veri taşıyan class” değil, API sınırını daha okunabilir hale getiren bir yapı.

Peki Her Şey Mükemmel mi?

DTO kullanmanın dezavantajı yokmuş gibi davranmak da doğru olmaz.

En net dezavantaj ekstra kod yazmak. Entity var, bir de DTO var, üstüne entity’den DTO’ya dönüşüm var. Küçük bir projede ilk bakışta bu biraz gereksiz görünebilir.

Bir diğer konu da mapping tarafı. Entity’ye yeni bir alan eklediğinizde, eğer bu alan response’ta da gösterilecekse DTO’ya ve dönüşüm koduna eklemeniz gerekir. Bunu unutmak mümkün.

Şu anda üzerinde çalıştığım projede manuel mapping yeterli oluyor çünkü yapı çok karmaşık değil. Ama proje büyürse ve DTO sayısı artarsa MapStruct gibi mapper kütüphaneleri tercih edilebilir.

Yani DTO kullanmak her şeyi otomatik olarak daha iyi yapmaz. Asıl mesele API sınırlarını bilinçli tasarlamak.

Error Response İçin DTO Kullanımı

DTO sadece başarılı response’lar için değil, hata response’ları için de kullanılabilir.

Şu anda üzerinde çalıştığım projede hata response yapısı için şöyle bir DTO kullanıyorum:

public record ErrorResponse(
 LocalDateTime timestamp,
 int status,
 String error,
 String message,
 String path
) {
}

Global exception handler içinde bu DTO’yu oluşturuyorum:

ErrorResponse response = new ErrorResponse(
 LocalDateTime.now(),
 status.value(),
 status.getReasonPhrase(),
 message,
 request.getRequestURI()
);

Bu sayede API hata verdiğinde response formatı daha düzenli oluyor. Frontend tarafı da her hata için farklı bir yapı beklemek zorunda kalmıyor.

Bunu küçük bir detay gibi görebiliriz ama gerçek projelerde tutarlı error response yapısı işleri ciddi anlamda kolaylaştırıyor.

Sonuç

DTO, Java backend projelerinde veri taşımak için kullanılan basit ama oldukça faydalı bir yapı.

Şu anda üzerinde çalıştığım projede DTO’ları özellikle request ve response modellerini ayırmak için kullandım. Entity’leri direkt dışarı açmak yerine, frontend’in ihtiyacı olan verileri DTO’larla döndürmek daha temiz ve kontrollü bir yapı sağladı.

Java 21 kullandığım için DTO tarafında record kullanmak benim için iyi bir tercih. Daha az kod yazıyorum, DTO’nun amacı daha net görünüyor ve immutable yapı sayesinde model daha güvenli hale geliyor.

Gerçek projelerde çalıştıkça DTO’nun sadece veri taşıyan bir nesne olmadığını, aynı zamanda API tasarımının önemli bir parçası olduğunu daha net gördüm. Özellikle API sınırlarını kontrollü şekilde yönetmek istediğinizde DTO’nun değeri daha belirgin hale geliyor.

Kısaca:

DTO = Katmanlar arasında kontrollü veri taşımak.

Okuduğunuz için teşekkürler. Bir sonraki yazıda görüşmek üzere.

Java #Dto #DataTransferObject


메타데이터
post_id
2d158c5278a7
slug
dto-nedir-2d158c5278a7
url
https://medium.com/@edakas/dto-nedir-2d158c5278a7
canonical_url
https://medium.com/@edakas/dto-nedir-2d158c5278a7
author_url
https://medium.com/@edakas
status
ok
fetched_at
2026-06-11 05:11:55