공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 14 / 22

인코딩·한글 실무

UTF-8·EUC-KR·CP949, BOM, 깨진 글자 진단, 바이트 길이, 파일명
섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

2. 핵심 원리

2.1 문자, 코드 포인트, 인코딩의 구분

문자는 사람이 읽는 기호이고, 코드 포인트는 유니코드가 문자마다 매긴 번호입니다. 예를 들어 한의 코드 포인트는 U+D55C입니다. 인코딩은 이 코드 포인트를 실제 바이트로 바꾸는 규칙이며, 같은 문자도 인코딩에 따라 바이트가 완전히 달라집니다.

자바의 String은 메모리에서 UTF-16 코드 유닛으로 문자를 들고 있습니다. 파일이나 네트워크로 내보낼 때 비로소 getBytes(Charset)으로 특정 인코딩의 바이트가 됩니다. "인코딩이 깨진다"는 말은 이 변환·역변환 두 지점 중 한쪽에서 잘못된 Charset을 썼다는 뜻입니다.

2.2 UTF-8 vs EUC-KR vs CP949(MS949)

세 인코딩은 한글을 담는 방식이 다릅니다. 실무에서 마주치는 차이는 아래 표와 같습니다.

인코딩 한글 바이트 수 완성형 한글 확장 한글(똠·펲 등)
UTF-8 3바이트 지원 지원
EUC-KR 2바이트 2350자만 지원 미지원
CP949(MS949) 2바이트 지원 지원(확장 영역)

EUC-KR 은 옛 KS X 1001 완성형 2350자만 담을 수 있어 "똠, 뷁" 같은 조합형 확장 한글을 표현하지 못합니다. 윈도우가 쓰던 CP949(MS949)는 EUC-KR 을 그대로 포함하면서 남는 바이트 영역에 확장 한글을 추가한 규격입니다. 자바 Charset.forName("EUC-KR")과 "MS949"는 이 차이 그대로 동작하므로, 레거시 연동에서는 반드시 어느 쪽인지 확인해야 합니다.

2.3 BOM 의 정체

BOM(Byte Order Mark)은 파일 맨 앞에 붙는 EF BB BF(UTF-8 기준) 바이트로, 이 파일이 어떤 인코딩·바이트 순서인지 알려주는 표식입니다. UTF-8 은 바이트 순서 문제가 없어 원래 BOM 이 필요 없지만, 관행적으로 Windows 프로그램이 UTF-8 임을 표시하려고 붙입니다.

Excel 은 BOM 이 없는 UTF-8 CSV 를 EUC-KR 로 오인해 한글을 깨뜨립니다. 그래서 Excel 로 열 CSV 를 만들 때는 일부러 BOM 을 붙입니다. 반대로 이 CSV 를 자바로 다시 읽을 때 BOM 을 제거하지 않으면, 첫 줄 첫 글자 앞에 보이지 않는 U+FEFF 문자가 남아 문자열 비교가 실패합니다.

2.4 JDK 기본 인코딩과 JEP 400

JDK 17 이하는 OS 로케일에 따라 file.encoding 기본값이 MS949나 EUC-KR이 되는 경우가 있었습니다. JDK 18 부터는 JEP 400 으로 file.encoding 기본값이 플랫폼과 무관하게 UTF-8 로 고정됐습니다. 이 레슨은 JDK 21 기준이라 별도 옵션 없이도 UTF-8 이 기본입니다.

콘솔 출력은 별도로 stdout.encoding이 OS 코드페이지를 따르는 경우가 있어, Windows 콘솔에서 한글이 깨지면 -Dstdout.encoding=UTF-8을 주거나 chcp 65001로 콘솔 코드페이지를 바꿉니다. -Dfile.encoding=UTF-8은 파일 입출력 기본값, -Dstdout.encoding=UTF-8은 콘솔 출력 전용이라 용도가 다릅니다.

2.5 깨진 글자 유형별 진단표

증상만 보고도 원인을 좁힐 수 있습니다.

증상 원인
??? 인코딩 불가 문자를 물음표로 치환(손실 발생)
�(네모, U+FFFD) 잘못된 바이트를 대체 문자로 치환
占쏙옙 UTF-8 바이트를 EUC-KR/CP949 로 오인 디코딩
ë° 같은 라틴 조합 UTF-8 바이트를 ISO-8859-1 로 오인 디코딩

???는 이미 손실이 일어난 상태라 원본 복구가 불가능합니다. 반면 占쏙옙이나 ë° 패턴은 원본 바이트가 그대로 남아있을 가능성이 높아, 올바른 인코딩으로 다시 디코딩하면 복구되는 경우가 많습니다.

2.6 문자 길이 vs 바이트 길이

자바 String.length()는 UTF-16 코드 유닛 개수이지, 저장될 바이트 수가 아닙니다. 고정 길이 전문(EDI, 은행권 배치)이나 VARCHAR2(100 BYTE)처럼 바이트 기준 컬럼에 한글을 넣으면, 글자 수 기준으로 자를 때 한글 중간에서 잘려 마지막 글자가 깨집니다.

Oracle VARCHAR2는 BYTE와 CHAR 두 가지 길이 시맨틱을 지원합니다. VARCHAR2(100 CHAR)는 글자 수 100자를 보장하지만, VARCHAR2(100 BYTE)(기본값)는 바이트 100을 보장해 한글이 섞이면 실제 저장 가능한 글자 수가 줄어듭니다. NLS_CHARACTERSET이 AL32UTF8이면 한글 1자가 3바이트를 쓰므로 여유를 넉넉히 잡아야 합니다.

2.7 한글 자모 정규화 NFC/NFD

유니코드는 같은 글자를 완성형(NFC, 한 한 코드 포인트)과 분해형(NFD, ㅎ+ㅏ+ㄴ 자모 조합)으로 모두 표현할 수 있습니다. macOS 는 파일명을 NFD 로 저장하는 관행이 있어, 맥에서 만든 zip·파일을 Windows·Linux 서버로 옮기면 화면에는 같은 글자로 보여도 바이트가 달라 파일을 못 찾는 문제가 생깁니다.

java.text.Normalizer로 Form.NFC·Form.NFD 간 변환을 할 수 있습니다. 외부에서 들어온 파일명은 저장 전에 NFC 로 정규화해두는 것이 안전합니다.

2.8 HTTP·ZIP 파일명 인코딩

HTTP 다운로드 응답의 Content-Disposition 헤더는 기본적으로 ASCII 만 허용합니다. 한글 파일명은 RFC 5987 형식인 filename*=UTF-8''인코딩된값으로 넣고, 구형 브라우저를 위해 ASCII 대체 filename="download.xlsx"도 함께 둡니다.

ZIP 파일 포맷은 표준 항목 이름 인코딩을 CP437 로 정의하고 있어, 한글 파일명을 담은 zip 을 압축 프로그램마다 다르게 해석해 깨지는 경우가 흔합니다. 자바 ZipOutputStream은 UTF-8 로 이름을 쓰고 UTF-8 플래그(APPNOTE 0x0800)를 세팅하는 라이브러리(Apache Commons Compress 등)를 쓰면 대부분 호환됩니다.

2.9 레거시 연동과 Spring 설정 키

레거시 EUC-KR 시스템과 연동할 때는 "경계에서만 변환"이 원칙입니다. 애플리케이션 내부는 UTF-8(자바 String)로 통일하고, 레거시로 나가는 전문·파일만 그 순간에 EUC-KR 로 인코딩합니다. 중간에 여러 번 인코딩을 바꾸면 어디선가 반드시 깨집니다.

Spring Boot 관련 설정 키는 아래만 기억해도 충분합니다.

설정 키 역할
server.servlet.encoding.charset HTTP 요청·응답 기본 인코딩
spring.datasource.url 의 characterEncoding=UTF-8 JDBC 연결 인코딩(MySQL 계열)
file.encoding VM 옵션 파일 입출력 기본 인코딩(JDK 18+ 는 UTF-8 기본)
핵심 원리
  • 2.1 문자, 코드 포인트, 인코딩의 구분
  • 2.2 UTF-8 vs EUC-KR vs CP949(MS949)
  • 2.3 BOM 의 정체
  • 2.4 JDK 기본 인코딩과 JEP 400
  • 2.5 깨진 글자 유형별 진단표
  • 2.6 문자 길이 vs 바이트 길이
  • 2.7 한글 자모 정규화 NFC/NFD
  • 2.8 HTTP·ZIP 파일명 인코딩
  • 2.9 레거시 연동과 Spring 설정 키
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제