
Room TypeConverter는 단순 문법이 아니라, 앱 안의 객체 표현과 SQLite 컬럼 표현 사이를 번역하는 장치입니다. 결론부터 말하면 컬럼 하나로 납작하게 저장해도 되는 값인지 먼저 판단한 뒤 converter를 붙여야 Room 설계가 오래 버팁니다.
이 글에서는 왜 Room이 객체를 그대로 저장하지 못하는지, enum과 LocalDateTime은 왜 converter가 잘 맞는지, List나 복합 객체는 언제 테이블 분리가 더 나은지까지 실무 기준으로 정리하겠습니다.
왜 그대로 못 넣을까
Room은 Kotlin 객체 저장소가 아니라 SQLite 위에서 동작하는 ORM입니다. 그래서 각 필드를 결국 SQLite가 이해하는 컬럼 값으로 바꿔야 합니다. Int, Long, String처럼 규칙이 분명한 값은 괜찮지만 enum, LocalDateTime, List 같은 값은 어떤 저장 규칙을 써야 할지 개발자가 명시해야 합니다.

한 줄로 보면
TypeConverter는 앱 타입을 persistence 가능한 DB 타입으로 바꾸고, 다시 읽을 때 원래 앱 타입으로 복원하는 양방향 규칙입니다. 즉 저장 시점과 읽기 시점의 번역 메서드를 Room에 등록하는 방식입니다.

예시 1: enum
enum은 값 하나를 의미 있는 코드로 표현할 때가 많아서 converter와 궁합이 좋습니다. 컬럼 하나로 충분하고, 문자열로 저장하면 디버깅도 쉽습니다.
enum class Priority {
LOW,
MEDIUM,
HIGH,
}
@Entity
data class TaskEntity(
@PrimaryKey val id: Long,
val title: String,
val priority: Priority,
)
class TaskConverters {
@TypeConverter
fun fromPriority(value: Priority): String = value.name
@TypeConverter
fun toPriority(value: String): Priority = Priority.valueOf(value)
}여기서도 ordinal보다는 의미가 남는 문자열 저장이 더 안전한 경우가 많습니다. enum 순서가 바뀌면 ordinal은 migration과 디버깅을 더 거칠게 만들 수 있기 때문입니다.
예시 2: LocalDateTime
LocalDateTime은 객체 하나처럼 보이지만 저장 방식은 하나가 아닙니다. 문자열로 저장할지, epoch 숫자로 저장할지, timezone 해석을 어떻게 둘지 먼저 정해야 합니다. 즉 converter는 문법이 아니라 저장 정책에 가깝습니다.
class DateTimeConverters {
@TypeConverter
fun fromLocalDateTime(value: LocalDateTime?): String? {
return value?.toString()
}
@TypeConverter
fun toLocalDateTime(value: String?): LocalDateTime? {
return value?.let(LocalDateTime::parse)
}
}DB를 사람이 직접 읽어야 하는지, 정렬과 비교를 숫자 기준으로 빠르게 하고 싶은지, 서버와 교환 규칙이 무엇인지에 따라 더 자연스러운 포맷이 달라집니다.
예시 3: List 저장
List
- 저장만 되면 되는 작은 부가 표현인가
- 값 내부를 SQL에서 검색할 일은 없는가
- 정렬, 집계, 조인 요구가 붙을 가능성은 낮은가
- 변환 규칙이 나중에도 안정적으로 유지될 수 있는가
즉 객체냐 원시값이냐보다 그 값이 DB 안에서 독립적으로 질의될 정보인가를 먼저 봐야 합니다.
남용하면 생기는 문제

- DB에서는 단순해 보이지만 쿼리는 오히려 불편해진다
- 변환 규칙이 바뀌면 schema 변경이 없어 보여도 데이터 의미가 달라질 수 있다
- DB Inspector에서 값이 난독화된 문자열처럼 보여 디버깅이 느려진다
- SQLite의 조회·정렬·관계 표현 장점을 스스로 줄이게 된다
ProvidedTypeConverter는 언제
어떤 converter는 공통 serializer 설정이나 의존성이 필요할 수 있습니다. 이럴 때 Room의 ProvidedTypeConverter를 검토할 수 있습니다. 핵심은 converter가 필요하지만 static한 유틸 함수처럼 두고 싶지 않을 때, database builder 쪽에서 명시적으로 제공한다는 점입니다.
빠르게 고르는 기준
- 컬럼 하나로 표현해도 되는 값인가
- 값 내부를 SQL에서 검색하지 않아도 되는가
- 사람이 DB 값을 봐도 의미를 이해할 수 있는가
- 변환 규칙이 바뀔 때 migration까지 함께 설명할 수 있는가
대체로 “예”가 많으면 converter가 잘 맞고, “아니오”가 많으면 컬럼 분리나 관계 설계를 먼저 보는 편이 안전합니다.
컬럼 하나로 충분한지가 먼저다
TypeConverter를 쓸지 말지는 객체가 복잡하냐가 아니라, 그 값을 컬럼 하나로 납작하게 저장해도 나중 질의와 해석이 안 꼬이는지부터 보는 편이 좋습니다.
검색, 집계, 정렬, 관계가 중요해지는 값이라면 converter보다 테이블 분리가 더 자연스러울 수 있습니다.
마무리
Room TypeConverter는 Room의 부족함을 억지로 메우는 해킹이 아니라, 앱 객체와 DB 표현의 경계를 개발자가 명시하는 공식 통로에 가깝습니다. enum, 날짜/시간, 작은 value object에는 매우 유용하지만, 검색성과 관계가 중요한 구조까지 전부 문자열 하나로 감추기 시작하면 쿼리와 migration이 같이 어려워질 수 있습니다.
함께 보면 좋은 글로는 Room migration 쉽게 이해하기, 화면 상태는 어디에 두는 게 맞을까, 안드로이드 single activity 구조는 왜 많아졌을까가 있습니다. 공식 기준은 Referencing complex data using Room, TypeConverter API reference, TypeConverters API reference, ProvidedTypeConverter API reference를 같이 보면 더 선명해집니다.