JCF 의 HashMap 코드를 보다가 SerialVersionUID 를 보고 호기심에 학습한 내용

1. 클래스의 기본 해시값을 통한 SerialVersionUID 의 문제
- 클래스의 구조가 변경되었을 때 오류가 발생한다.
- 예시: 멤버 변수 타입 변경, 멤버 변수 삭제
- 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체도 버전업을 통해 SerialVersionUID가 변경될 경우가 있으므로 예상하지 못한 오류가 발생할 수 있다.
2. SerialVersionUID??
- SUID 는 직렬화와 역직렬화 과정에서 값이 서로 맞는지 확인한 후에 처리한다.(맞지 않다면 InvalidClassException 를 반환한다.)
- 자바의 직렬화 스펙 정의를 살펴보면 SUID 값은 필수가 아니며 선언되어 있지 않으면 클래스의 기본 해시값을 사용한다. (이 값들은 클래스의 이름, 생성자 등과 같이 클래스의 구조를 이용해서 생성)
필드 추가 후 역직렬화가 불가능함을 확인하는 테스트 코드
class DefaultSerialEntity implements Serializable {
private String title;
private String pressName;
private String reporterName;
// 새로운 필드 추가
private String phoneNumber;
public DefaultSerialEntity(String title, String pressName, String reporterName) {
this.title = title;
this.pressName = pressName;
this.reporterName = reporterName;
}
@Override
public String toString() {
return "Article{" +
"title='" + title + '\'' +
", pressName='" + pressName + '\'' +
", reporterName='" + reporterName + '\'' +
'}';
}
}
// test code
class DefaultSerialEntityTest {
@Test
@DisplayName("DefaultSerialEntity 에 phoneNumber 필드를 추가할 경우 다른 역직렬화 값을 반환하여 예외를 반환한다")
void defaultSerializeWhenAddField() throws IOException, ClassNotFoundException {
//given, when
// phoneNumber 필드 추가 이전 직렬화 값
String serializedByteStrBeforeFieldAdd = "rO0ABXNyACtjb20uY29vcGVyLnNlcmlhbGl6YWJsZS5EZWZhdWx0U2VyaWFsRW50aX" +
"R54Lm3/1et2ggCAANMAAlwcmVzc05hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAxyZXBvcnRlck5hbWVxAH4AAUwABXRpdGxlc" +
"QB+AAF4cHQAC3ByZXNzTmFtZTExdAAOcmVwb3J0ZXJOYW1lMTF0AAbsnbTrpoQ=";
//then
assertThatThrownBy(() -> SerializableUtil.<DefaultSerialEntity>deSerializeMethod(serializedByteStrBeforeFieldAdd))
.isInstanceOf(InvalidClassException.class);
}
}
필드 추가 후 정상적으로 역직렬화가 되는 것을 확인하는 테스트 코드
public class SerialUIDEntity implements Serializable {
@Serial
private static final long serialVersionUID = -7116771609929351855L;
private String title;
private String pressName;
private String reporterName;
// 새로운 필드 추가
private String phoneNumber;
public SerialUIDEntity(String title, String pressName, String reporterName) {
this.title = title;
this.pressName = pressName;
this.reporterName = reporterName;
}
@Override
public String toString() {
return "SerialUIDEntity{" +
"title='" + title + '\'' +
", pressName='" + pressName + '\'' +
", reporterName='" + reporterName + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
'}';
}
}
// test code
class SerialUIDEntityTest {
@Test
@DisplayName("serialVersionUID 을 선언하면 클래스 정보 관계없이 serialVersionUID 기준으로 직렬화된다.")
void SerialVersionUID() throws IOException, ClassNotFoundException {
//given, when
// phoneNumber 필드 추가 이전 직렬화 값 : new SerialUIDEntity("title222", "pressName222", "reporter222");
String serializedByteStrBeforeFieldAdd = "rO0ABXNyACdjb20uY29vcGVyLnNlcmlhbGl6YWJsZS5TZXJpYWxVSURFbnRpdHmdPCX" +
"tUpN9UQIAA0wACXByZXNzTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wADHJlcG9ydGVyTmFtZXEAfgABTAAFdGl0bGVxAH4AAXh" +
"wdAAMcHJlc3NOYW1lMjIydAALcmVwb3J0ZXIyMjJ0AAh0aXRsZTIyMg==";
//then
Assertions.assertThat(SerializableUtil.<SerialUIDEntity>deSerializeMethod(serializedByteStrBeforeFieldAdd))
.isInstanceOf(SerialUIDEntity.class);
}
}
3. 클래스의 기본 해시값을 통한 SerialVersionUID 의 문제
- 클래스의 구조가 변경 되었을 때 오류가 발생한다.
- 예시: 멤버 변수 타입 변경, 멤버 변수 삭제
- 클래스의 기본 해시값을 통한 SerialVersionUID 의 문제
- 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체도 버전업을 통해 SerialVersionUID 가 변경될 경우가 있으므로 예상하지 못한 오류가 발생할 수 있다.
4. 🚨 SerialVersionUID 는 되도록이면 개발자가 직접 관리하자!!
- intellij 에서는 serialVersionUID 자동 생성을 제공하는 옵션을 제공하기 때문에 꼭 옵션을 추가하도록 하자!! (이왕이면 경고까지 띄우도록 하자!)
- (File > Settings > Serializable class without ‘serialVersionUID’)

5. Serialazable 의 권장하지 않지만 사용할 때는 주의해서 사용하자.
- 악의적인 공격 요소가 많다.
- readObject : 해당 메서드는 클래스 패스에 있고 Serializable interface 를 구현한 모든 타입을 생성할 수 있기 때문이다.
- 역직렬화 필터링 을 사용해 안전하다고 판단되는 클래스 화이트 리스트 와 같은 목록을 역직렬화를 하는데 제공해야 한다.
6. References
'java > summary' 카테고리의 다른 글
자바에서 동시성은 왜 발생할까? (4) | 2025.03.23 |
---|---|
Java Local Cache 비교하기 (0) | 2025.03.09 |
Virtual Thread 무엇일까? summary (0) | 2024.11.28 |
자바 비동기로 카페 콘솔 예제 만들기 (1) | 2024.11.26 |
어댑터 패턴(adapter pattern) (1) | 2024.07.09 |
JCF 의 HashMap 코드를 보다가 SerialVersionUID 를 보고 호기심에 학습한 내용

1. 클래스의 기본 해시값을 통한 SerialVersionUID 의 문제
- 클래스의 구조가 변경되었을 때 오류가 발생한다.
- 예시: 멤버 변수 타입 변경, 멤버 변수 삭제
- 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체도 버전업을 통해 SerialVersionUID가 변경될 경우가 있으므로 예상하지 못한 오류가 발생할 수 있다.
2. SerialVersionUID??
- SUID 는 직렬화와 역직렬화 과정에서 값이 서로 맞는지 확인한 후에 처리한다.(맞지 않다면 InvalidClassException 를 반환한다.)
- 자바의 직렬화 스펙 정의를 살펴보면 SUID 값은 필수가 아니며 선언되어 있지 않으면 클래스의 기본 해시값을 사용한다. (이 값들은 클래스의 이름, 생성자 등과 같이 클래스의 구조를 이용해서 생성)
필드 추가 후 역직렬화가 불가능함을 확인하는 테스트 코드
class DefaultSerialEntity implements Serializable {
private String title;
private String pressName;
private String reporterName;
// 새로운 필드 추가
private String phoneNumber;
public DefaultSerialEntity(String title, String pressName, String reporterName) {
this.title = title;
this.pressName = pressName;
this.reporterName = reporterName;
}
@Override
public String toString() {
return "Article{" +
"title='" + title + '\'' +
", pressName='" + pressName + '\'' +
", reporterName='" + reporterName + '\'' +
'}';
}
}
// test code
class DefaultSerialEntityTest {
@Test
@DisplayName("DefaultSerialEntity 에 phoneNumber 필드를 추가할 경우 다른 역직렬화 값을 반환하여 예외를 반환한다")
void defaultSerializeWhenAddField() throws IOException, ClassNotFoundException {
//given, when
// phoneNumber 필드 추가 이전 직렬화 값
String serializedByteStrBeforeFieldAdd = "rO0ABXNyACtjb20uY29vcGVyLnNlcmlhbGl6YWJsZS5EZWZhdWx0U2VyaWFsRW50aX" +
"R54Lm3/1et2ggCAANMAAlwcmVzc05hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAxyZXBvcnRlck5hbWVxAH4AAUwABXRpdGxlc" +
"QB+AAF4cHQAC3ByZXNzTmFtZTExdAAOcmVwb3J0ZXJOYW1lMTF0AAbsnbTrpoQ=";
//then
assertThatThrownBy(() -> SerializableUtil.<DefaultSerialEntity>deSerializeMethod(serializedByteStrBeforeFieldAdd))
.isInstanceOf(InvalidClassException.class);
}
}
필드 추가 후 정상적으로 역직렬화가 되는 것을 확인하는 테스트 코드
public class SerialUIDEntity implements Serializable {
@Serial
private static final long serialVersionUID = -7116771609929351855L;
private String title;
private String pressName;
private String reporterName;
// 새로운 필드 추가
private String phoneNumber;
public SerialUIDEntity(String title, String pressName, String reporterName) {
this.title = title;
this.pressName = pressName;
this.reporterName = reporterName;
}
@Override
public String toString() {
return "SerialUIDEntity{" +
"title='" + title + '\'' +
", pressName='" + pressName + '\'' +
", reporterName='" + reporterName + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
'}';
}
}
// test code
class SerialUIDEntityTest {
@Test
@DisplayName("serialVersionUID 을 선언하면 클래스 정보 관계없이 serialVersionUID 기준으로 직렬화된다.")
void SerialVersionUID() throws IOException, ClassNotFoundException {
//given, when
// phoneNumber 필드 추가 이전 직렬화 값 : new SerialUIDEntity("title222", "pressName222", "reporter222");
String serializedByteStrBeforeFieldAdd = "rO0ABXNyACdjb20uY29vcGVyLnNlcmlhbGl6YWJsZS5TZXJpYWxVSURFbnRpdHmdPCX" +
"tUpN9UQIAA0wACXByZXNzTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wADHJlcG9ydGVyTmFtZXEAfgABTAAFdGl0bGVxAH4AAXh" +
"wdAAMcHJlc3NOYW1lMjIydAALcmVwb3J0ZXIyMjJ0AAh0aXRsZTIyMg==";
//then
Assertions.assertThat(SerializableUtil.<SerialUIDEntity>deSerializeMethod(serializedByteStrBeforeFieldAdd))
.isInstanceOf(SerialUIDEntity.class);
}
}
3. 클래스의 기본 해시값을 통한 SerialVersionUID 의 문제
- 클래스의 구조가 변경 되었을 때 오류가 발생한다.
- 예시: 멤버 변수 타입 변경, 멤버 변수 삭제
- 클래스의 기본 해시값을 통한 SerialVersionUID 의 문제
- 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체도 버전업을 통해 SerialVersionUID 가 변경될 경우가 있으므로 예상하지 못한 오류가 발생할 수 있다.
4. 🚨 SerialVersionUID 는 되도록이면 개발자가 직접 관리하자!!
- intellij 에서는 serialVersionUID 자동 생성을 제공하는 옵션을 제공하기 때문에 꼭 옵션을 추가하도록 하자!! (이왕이면 경고까지 띄우도록 하자!)
- (File > Settings > Serializable class without ‘serialVersionUID’)

5. Serialazable 의 권장하지 않지만 사용할 때는 주의해서 사용하자.
- 악의적인 공격 요소가 많다.
- readObject : 해당 메서드는 클래스 패스에 있고 Serializable interface 를 구현한 모든 타입을 생성할 수 있기 때문이다.
- 역직렬화 필터링 을 사용해 안전하다고 판단되는 클래스 화이트 리스트 와 같은 목록을 역직렬화를 하는데 제공해야 한다.
6. References
'java > summary' 카테고리의 다른 글
자바에서 동시성은 왜 발생할까? (4) | 2025.03.23 |
---|---|
Java Local Cache 비교하기 (0) | 2025.03.09 |
Virtual Thread 무엇일까? summary (0) | 2024.11.28 |
자바 비동기로 카페 콘솔 예제 만들기 (1) | 2024.11.26 |
어댑터 패턴(adapter pattern) (1) | 2024.07.09 |