최근 매일 아침 면접 질문을 보내주는 'meail-mail' 서비스를 통해 흥미로운 질문 하나를 받았습니다.

"Java에서 Record를 Dto로 사용하는 이유가 무엇인가?"
평소 프로젝트를 진행하면서 저는 Java Record를 DTO로 아주 자연스럽게 활용해 왔습니다.
불변성을 바탕으로 데이터의 신뢰성을 높일 수 있었고, 값 기반 비교와 간결한 구조 역시 큰 장점이라고
생각했습니다.
물론 모든 상황에서 Record를 고집한 것은 아닙니다.
하지만 DTO처럼 데이터를 전달하는 객체를 설계할 때만큼은, Record가 꽤 좋은 선택지라고 느껴왔습
니다.
그런데 이 질문을 마주한 순간, 익숙하게 사용해 왔던 선택을 다시 돌아보게 되었습니다.
나는 왜 Record를 DTO로 사용해 왔을까?
그리고 그 이유를 단순한 취향이 아니라, 누군가를 설득할 수 있는 기술적 근거로 설명할 수 있을까?
이번 글에서는 이런 질문에서 출발해,
Record가 무엇인지, 왜 DTO와 잘 맞는지, 그리고 어떤 한계가 있는지까지 차근차근 정리해보려 합니
다.
익숙해서 당연하게 여겼던 선택을 다시 언어와 설계의 관점에서 살펴보며,
제가 사용해 온 이유를 조금 더 분명하게 정리해보겠습니다.
https://share.google/78ZITwMvkgJEDWaQt
매일메일 - 기술 면접 질문 구독 서비스
기술 면접 질문을 매일매일 메일로 보내드릴게요!
www.maeil-mail.kr
Record는 무엇인가?
Record는 Java에서 데이터를 더 간결하고 명확하게 표현하기 위해 도입된 특별한 형태의 클래스입니다.
기존에도 클래스만으로 데이터를 담는 객체를 만드는 것은 가능했습니다.
하지만 단순히 값을 저장하고 전달하기 위한 객체를 만들 때조차 필드, 생성자, 접근자, equals(),
hashCode(), toString() 같은 반복 코드가 계속 필요했습니다.
문제는 이런 코드가 많아질수록,
정작 이 객체가 무슨 역할을 하는지보다
어떻게 작성되어 있는지에 더 눈이 가게 된다는 점이었습니다.
Record는 이런 불편함을 줄이기 위해 등장했습니다.
데이터를 담는 객체라면 자주 반복되는 요소를 언어 차원에서 정리하고,
개발자가 객체의 본질적인 역할에 더 집중할 수 있도록 만든 것입니다.
예를 들어 다음과 같은 DTO가 있다고 가정해 보겠습니다.
public class MemberDto {
private final String name;
private final String email;
private final int age;
public MemberDto(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public int getAge() {
return age;
}
}
이 코드는 분명 문제 없이 동작합니다.
하지만 이 객체가 단순히 데이터를 담아 전달하는 용도라면, 구조에 비해 작성해야 할 코드가 다소 많다
고 느껴질 수 있습니다.
같은 객체를 Record로 표현하면 훨씬 간결해집니다.
public record MemberDto(String name, String email, int age) {}
이 한 줄만으로도 이 객체가 어떤 데이터를 가지는지 바로 드러납니다.
또 생성자, 접근자 equals(), hashCode(), toString() 같은 메서드도 함께 제공됩니다.
즉, Record의 핵심은 단순히 코드를 짧게 줄이는 데 있지 않습니다.
더 중요한 것은, 이 객체가 행동하는 객체가 아니라 데이터를 표현하는 객체라는 점을 더 분명하게 드러
내 준다는 데 있습니다.
이런 특성 때문에 Record는
조회 결과를 담거나, 요청과 응답을 전달하거나,
계층 간 데이터를 주고받는 구조에서 특히 잘 어울립니다.
결국 Record는
"이 객체는 여러 책임을 가진 일반 클래스가 아니라,
정해진 데이터를 담기 위한 구조다"
라는 의도를 명확하게 표현하는 방식이라고 볼 수 있습니다.
왜 Record를 DTO로 사용하는가
그렇다면 이제 처음 질문으로 다시 돌아가 보겠습니다.
왜 Record를 DTO로 사용하는가?
이 질문에 답하려면 먼저 DTO가 어떤 역할을 하는 객체인지 생각해볼 필요가 있습니다.
DTO는 계층 간 데이터를 전달하기 위한 객체입니다.
즉, 복잡한 비즈니스 로직을 수행하기보다는 필요한 데이터를 담아, 다른 계층으로 정확하게 전달하는
데 목적이 있습니다.
이 점에서 Record는 DTO와 꽤 잘 맞습니다.
왜냐하면 Record 역시 데이터를 간결하게 표현하고 전달하는 데 초점이 맞춰진 구조이기 때문입니다.
가장 먼저 떠올릴 수 있는 이유는 불변성입니다.
DTO는 한 번 생성된 뒤 요청 객체로 전달되거나, 응답 객체로 반환되면서 여러 계층을 오가게 됩니다.
이 과정에서 값이 중간에 바뀌기 시작하면 데이터 흐름을 추적하기 어려워지고, 예상하지 못한 부작용이
생길 수 있습니다.
Record는 이런 부분에서 비교적 안정적인 구조를 제공합니다.
생성 이후 상태를 다시 바꾸는 방식보다는 처음 정해진 값을 그대로 유지하는데 더 적합하기 때문입니
다. 그래서 DTO 처럼 "한 시점의 데이터"를 담아 전달하는 객체와 자연스럽게 잘 어울립니다.
또 다른 이유는 값 중심 구조입니다.
DTO는 보통 객체의 정체성보다, 그 안에 어떤 값이 들어 있는지가 더 중요합니다.
예를 들어 사용자 응답 DTO라면, 중요한 것은 그 객체가 누구인지가 아니라 id, name, email 같
은 데이터입니다.
Record는 바로 이런 값 중심 객체를 표현하기에 적합합니다.
필드 기반으로 객체를 정의하고, 값 중심 비교에 필요한 메서드도 함께 제공하기 때문에
DTO가 의도하는 구조와 방향이 잘 맞아떨어집니다.
마지막으로 Record는 DTO를 DTO답게 보이게 만든다는 장점이 있습니다.
일반 클래스로도 DTO를 만들 수는 있지만, 그 경우 생성자, getter, 여러 메서드가 함께 붙으면서
이 객체가 단순 전달용인지, 다른 책임까지 갖는 객체인지 한눈에 드러나지 않을 때가 있습니다.
반면 Record는 선언 자체만으로
“이 객체는 정해진 데이터를 담기 위한 구조다”
라는 점을 비교적 분명하게 보여줍니다.
결국 Record를 DTO로 사용하는 이유는 단순히 코드가 짧아져서만은 아닙니다.
DTO가 본래 가지는 성격, 즉 데이터 전달 중심, 불변성, 값 중심 구조, 간결한 표현과
Record의 설계 방향이 잘 맞기 때문입니다.
그래서 Record는 DTO를 단지 편하게 만드는 문법이 아니라,
DTO의 역할을 더 분명하게 드러내는 방식이라고 볼 수 있습니다.
모든 Record가 DTO인 것은 아니다
여기서 한 가지는 분명히 구분할 필요가 있습니다.
Record를 사용했다고 해서, 그 객체가 곧바로 DTO가 되는 것은 아닙니다.
Record는 어디까지나 Java가 제고하는 객체 표현 방식입니다.
반면 DTO는 객체의 역할을 의미합니다.
즉, Record는
“어떻게 표현할 것인가” 에 대한 선택이고,
DTO는
“무엇을 위해 사용하는가” 에 대한 개념입니다.
이 둘을 구분하지 않으면 Record와 DTO를 같은 말처럼 받아들이기 쉽습니다.
하지만 사실은 서로 다른 층위의 이야기입니다.
예를 들어 아래와 같은 Record를 생각해볼 수 있습니다.
public record Coordinates(double x, double y) {}
이 객체는 데이터를 담고 있다는 점에서는 DTO처럼 보일 수 있습니다.
하지만 실제 역할을 보면, 다른 계층으로 데이터를 전달하기 위한 용도라기보다
좌표라는 개념 자체를 표현하는 값 객체에 더 가깝습니다.
즉, 단순히 데이터를 담고 있다는 사실만으로 DTO가 되는 것은 아닙니다.
더 중요한 것은 그 객체가 어떤 맥락에서 사용되는지,
그리고 무슨 목적을 가지는지입니다.
결국 같은 Record라도
어떤 것은 DTO가 될 수 있고,
어떤 것은 VO나 다른 데이터 중심 객체가 될 수도 있습니다.
중요한 것은 문법이 아니라 역할입니다.
Record라는 형태를 사용했다는 이유만으로 DTO라고 단정할 수는 없습니다.
Record와 VO는 어떻게 다를까
이쯤에서 자연스럽게 이런 질문이 떠오를 수 있습니다.
그렇다면 Record와 VO는 어떻게 다를까?
둘은 얼핏 비슷해 보입니다.
값을 중심으로 다루고, 불변 객체처럼 설계하기 좋으며,
상태를 자주 변경하기보다는 데이터를 안정적으로 표현하는 데 잘 어울리기 때문입니다.
그래서 실제로 Record는 VO를 구현하는 방식으로도 충분히 활용될 수 있습니다.
하지만 그렇다고 해서 Record와 VO를 같은 개념으로 볼 수는 없습니다.
VO는 도메인 안에서 특정한 의미를 가진 값을 표현하는 객체입니다.
단순히 데이터를 담는 것에서 끝나는 것이 아니라,
그 값이 어떤 규칙을 가지는지, 어떤 의미를 담는지까지 함께 표현할 수 있습니다.
예를 들어 금액, 좌표, 주소 같은 값은
겉으로 보면 단순한 데이터 묶음처럼 보일 수 있습니다.
하지만 도메인 안에서는 분명한 의미와 규칙을 가질 수 있습니다.
반면 Record는
그러한 객체를 간결하게 표현할 수 있도록 도와주는 문법적 구조에 가깝습니다.
정리하면 이렇습니다.
- DTO와 VO는 객체의 역할
- Record는 객체를 작성하는 방식
그래서 VO를 Record로 표현할 수는 있습니다.
하지만 VO의 본질이 곧 Record인 것은 아닙니다.
마찬가지로 DTO 역시 Record로 만들 수는 있지만,
DTO라는 개념 자체가 Record와 동일한 것은 아닙니다.
이 구분을 이해하고 나면,
왜 Record를 DTO에 잘 활용할 수 있으면서도
동시에 “모든 Record가 DTO는 아니다” 라고 말할 수 있는지도 자연스럽게 이해됩니다.
Record를 사용할 때 한계는 없을까
여기까지 보면 Record는 DTO를 표현하기에 꽤 적합해 보입니다.
실제로도 많은 경우 잘 맞는 선택입니다.
다만 그렇다고 해서
모든 데이터 객체를 무조건 Record로 만드는 것이 정답은 아닙니다.
Record에도 분명한 한계와 제약이 있습니다.
가장 먼저 떠올릴 수 있는 부분은 확장성의 제약입니다.
Record는 일반 클래스처럼 자유롭게 상속 구조를 설계하는 데 적합하지 않습니다.
따라서 확장과 변경을 중심으로 설계해야 하는 객체라면,
Record보다 일반 클래스가 더 자연스러운 선택이 될 수 있습니다.
또한 Record는 생성 이후 상태를 바꾸지 않는 구조에 가깝기 때문에,
여러 단계에 걸쳐 값이 계속 변해야 하는 객체와는 잘 맞지 않을 수 있습니다.
이런 경우에는 오히려 일반 클래스가 더 명확한 표현이 됩니다.
그리고 Record는 데이터를 간결하게 표현하는 데 강점이 있지만,
복잡한 비즈니스 로직을 중심으로 움직이는 객체를 담기에는 다소 어울리지 않을 수 있습니다.
물론 메서드를 전혀 둘 수 없는 것은 아니지만,
설계 의도 자체가 “행동하는 객체”보다는 “데이터를 담는 객체”에 더 가깝기 때문입니다.
마지막으로 실무에서는 Java 버전도 고려해야 합니다.
프로젝트 환경이 Record를 안정적으로 사용할 수 있는 버전이 아니라면,
아무리 구조적으로 좋아 보여도 현실적으로 적용하기 어렵습니다.
즉, Record는 분명 좋은 도구이지만
모든 상황에 자동으로 들어맞는 만능 해답은 아닙니다.
정리하며
처음 질문으로 다시 돌아가 보면, 결국 핵심은 이것입니다.
왜 Record를 DTO로 사용하는가?
그 이유는 단순히 코드가 짧아져서만은 아닙니다.
DTO가 본래 가지는 성격과 Record의 설계 방향이 꽤 잘 맞기 때문입니다.
DTO는 데이터를 전달하기 위한 객체이고,
Record는 그런 데이터를 더 간결하고 명확하게 표현하기 위한 구조입니다.
불변성을 바탕으로 데이터를 비교적 안정적으로 다룰 수 있고,
값 중심 객체를 표현하기에도 적합하며,
반복 코드를 줄이면서 객체의 역할을 더 분명하게 드러낼 수 있습니다.
그래서 Record는 DTO를 구현할 때 꽤 설득력 있는 선택이 됩니다.
다만 중요한 것은
“Record니까 무조건 DTO다” 라고 받아들이는 것이 아니라,
이 객체가 정말 데이터를 전달하기 위한 객체인지,
아니면 도메인 의미와 규칙을 품는 객체인지를 먼저 구분하는 것입니다.
결국 좋은 설계는 문법 하나로 결정되지 않습니다.
어떤 도구를 쓰느냐보다,
그 도구가 지금 맡은 역할에 정말 잘 맞는지를 판단하는 것이 더 중요합니다.
이번 글을 정리하면서 저 역시
익숙해서 자연스럽게 사용해 왔던 선택을,
조금 더 분명한 근거 위에서 다시 바라볼 수 있었습니다.
'Programming > Java' 카테고리의 다른 글
| SDK manager - SDKMAN으로 JAVA 관리 방법 (0) | 2026.05.26 |
|---|---|
| 시간 개념을 Java로 옮기면 - Instant, LocalDateTime, ZonedDateTime 이해하기 (0) | 2026.03.19 |
