직렬화 (Serialization)

한눈에 보기

직렬화는 객체를 바이트 스트림으로 변환해 저장과 전송을 가능하게 하는 기술이다. serialVersionUID와 transient, 역직렬화 보안 취약점, JSON 등 대안 포맷과의 비교를 정리한다.

최종 업데이트 2026.09.17

직렬화(Serialization)는 메모리에 있는 객체를 바이트 스트림으로 변환해 파일 저장이나 네트워크 전송이 가능하게 만드는 기술이고, 역직렬화(Deserialization)는 그 바이트 스트림을 다시 객체로 복원하는 과정이다. 자바는 Serializable 인터페이스와 ObjectOutputStream, ObjectInputStream으로 언어 차원의 직렬화를 제공하지만, 버전 관리의 어려움과 심각한 보안 취약점 때문에 현대 실무에서는 JSON 같은 명시적 포맷이 우선 선택된다. 그래도 세션 클러스터링, 캐시, RMI 등에서 여전히 쓰이므로 동작 원리와 위험성을 정확히 알아야 한다.

기본 사용법과 동작 원리

자바 직렬화의 대상이 되려면 클래스가 마커 인터페이스(marker interface)인 Serializable을 구현해야 한다. 메서드가 하나도 없는 이 인터페이스는 직렬화를 허용한다는 표시일 뿐이고, 실제 변환은 JVM이 리플렉션(reflection)으로 객체의 필드를 순회하며 수행한다. 참조로 연결된 객체들도 함께 직렬화되므로, 객체 그래프 전체의 모든 구성 요소가 Serializable이어야 하며 아니면 NotSerializableException이 발생한다.

public class Member implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private transient String password; // 직렬화에서 제외
    private int age;
}

// 직렬화
try (ObjectOutputStream out =
         new ObjectOutputStream(new FileOutputStream("member.ser"))) {
    out.writeObject(new Member("kim", "secret", 20));
}

// 역직렬화, 생성자를 거치지 않고 객체가 복원된다
try (ObjectInputStream in =
         new ObjectInputStream(new FileInputStream("member.ser"))) {
    Member m = (Member) in.readObject();
}

중요한 특징은 역직렬화가 생성자를 호출하지 않는다는 점이다. JVM이 메모리를 할당하고 스트림의 값으로 필드를 직접 채우므로, 생성자에 넣어 둔 검증 로직이 전부 우회된다. 이것이 불변식(invariant) 훼손과 보안 문제의 근원이다.

serialVersionUID와 transient

serialVersionUID는 클래스 버전을 식별하는 값으로, 역직렬화 시 스트림에 기록된 값과 현재 클래스의 값이 다르면 InvalidClassException이 발생한다. 명시하지 않으면 컴파일러가 클래스 구조를 해싱해 자동 생성하는데, 필드나 메서드가 조금만 바뀌어도 값이 달라져 예전에 저장한 데이터를 읽지 못하게 된다. 그래서 직렬화 가능 클래스에는 serialVersionUID를 직접 선언하고, 호환이 깨지는 변경을 할 때만 의도적으로 값을 올리는 것이 원칙이다.

transient 키워드가 붙은 필드는 직렬화에서 제외되며 역직렬화 후 타입 기본값(참조형은 null)으로 채워진다. 비밀번호 같은 민감 정보, 소켓이나 스레드처럼 직렬화가 무의미한 자원, 다시 계산 가능한 캐시 필드에 사용한다. static 필드는 인스턴스 상태가 아니므로 애초에 직렬화되지 않는다. 더 세밀한 제어가 필요하면 private 메서드 writeObject와 readObject를 정의해 기본 직렬화를 보강하거나, readObject 안에서 검증과 방어적 복사를 수행할 수 있다.

역직렬화 보안 문제

자바 직렬화의 가장 큰 문제는 신뢰할 수 없는 데이터의 역직렬화가 원격 코드 실행(remote code execution)으로 이어질 수 있다는 것이다. ObjectInputStream은 스트림에 적힌 클래스라면 클래스패스에 있는 무엇이든 인스턴스화하며, 공격자는 readObject 실행 중 연쇄적으로 호출되는 클래스들을 조합한 가젯 체인(gadget chain)으로 임의 코드를 실행시킬 수 있다. 널리 쓰이던 라이브러리를 매개로 한 대형 취약점 사례가 실제로 여러 번 있었다.

방어책으로 Java 9부터 역직렬화 필터(ObjectInputFilter)가 도입되어 허용 클래스 화이트리스트, 그래프 깊이와 크기 제한을 걸 수 있다. 그러나 근본 원칙은 신뢰 경계를 넘어온 데이터에 자바 직렬화를 쓰지 않는 것이다. 자바 아키텍트들 역시 기본 직렬화를 설계 실수로 인정하고 장기적으로 대체하는 방향을 밝혀 왔다.

대안 포맷과 비교

구분자바 직렬화JSON (Jackson 등)바이너리 스키마 (Protobuf 등)
가독성없음, 자바 전용 바이너리사람이 읽을 수 있음없음, 스키마 필요
언어 호환자바끼리만언어 중립언어 중립
버전 관리serialVersionUID, 취약함필드 기반, 유연함필드 번호 기반, 강력함
보안가젯 체인 위험 큼상대적으로 안전상대적으로 안전
주 용도세션 복제, 캐시, RMIREST API고성능 내부 통신

실무 지침을 정리하면 이렇다. 외부와의 데이터 교환에는 JSON이나 스키마 기반 바이너리 포맷을 쓴다. 자바 직렬화를 불가피하게 쓴다면 serialVersionUID를 명시하고, 역직렬화 필터를 적용하고, readObject에서 불변식을 재검증한다. 참고로 레코드(Record)는 역직렬화 시에도 표준 생성자를 통과하도록 설계되어 검증 우회 문제가 없으므로, 직렬화 가능한 값 객체가 필요하다면 레코드가 더 안전한 선택이다.

면접에서 이렇게 물어본다

  • 직렬화와 역직렬화가 무엇이고 자바에서 어떤 클래스와 인터페이스로 수행하는지 설명해 보라.
  • serialVersionUID를 명시하지 않으면 어떤 문제가 생기는가.
  • transient 키워드의 역할과 사용 예를 들어 보라.
  • 역직렬화가 보안 취약점이 되는 원리와 방어 방법을 설명해 보라.
  • 실무에서 자바 기본 직렬화 대신 JSON을 쓰는 이유를 비교 관점에서 설명해 보라.
Java 문서 전체 보기

이 개념, 내 코드에서는 어떻게 물어볼까요

데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.

공고 넣고 시작하기