파일 경로와 내용 분리하기
Path는 파일의 위치를 표현하고 Files는 읽기와 쓰기 같은 작업을 제공합니다. Java 25에서는 문자열을 간단히 저장하고 읽을 수 있습니다.
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws Exception {
Path scoreFile = Path.of("score.txt");
Files.writeString(scoreFile, "Alex,1200");
String saved = Files.readString(scoreFile);
System.out.println(saved);
Files.deleteIfExists(scoreFile);
}
}
예상 출력:
Alex,1200
예제는 실행 폴더에 파일을 만들고 읽은 뒤 삭제합니다. 실제 서비스에서는 경로가 사용자가 허용한 폴더 안인지 확인하고, try-with-resources나 구체적 예외 처리를 적용해야 합니다.
JSON은 데이터 구조를 나타내는 텍스트
플레이어 데이터를 JSON으로 표현하면 다음과 같습니다.
{
"name": "Alex",
"health": 18,
"items": ["torch", "map"]
}
객체는 {}, 배열은 [], key와 문자열은 큰따옴표를 사용합니다. Java SE에는 범용 JSON 파서가 기본 포함되지 않으므로 입문 예제에서 존재하지 않는 표준 API를 만들지 않습니다. JSON 문자열을 직접 조립할 때는 따옴표 이스케이프를 주의하고, 실제 애플리케이션에서는 검증된 JSON 라이브러리를 선택하세요.
위 데이터를 ToolPado JSON Formatter에 붙여 넣으면 문법 오류와 들여쓰기를 확인할 수 있습니다. 도구는 입력을 브라우저 안에서 처리하지만 민감한 운영 데이터는 어떤 온라인 도구에도 넣지 않는 것이 안전합니다.
Base64 인코딩과 디코딩
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Main {
public static void main(String[] args) {
String original = "voxel-map";
String encoded = Base64.getEncoder().encodeToString(
original.getBytes(StandardCharsets.UTF_8)
);
String decoded = new String(
Base64.getDecoder().decode(encoded),
StandardCharsets.UTF_8
);
System.out.println(encoded);
System.out.println(decoded);
}
}
예상 출력:
dm94ZWwtbWFw
voxel-map
Base64 변환기로 같은 값이 왕복하는지 확인할 수 있습니다. Base64는 암호화가 아닙니다.
URL 인코딩
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) {
String query = "블록 지도";
String encoded = URLEncoder.encode(query, StandardCharsets.UTF_8);
System.out.println(encoded);
}
}
예상 출력:
%EB%B8%94%EB%A1%9D+%EC%A7%80%EB%8F%84
이는 query parameter의 값에 쓰는 form encoding입니다. 전체 URL을 한 번에 인코딩하지 말고 값 부분을 목적에 맞게 처리하세요. URL 인코더에서 한글과 공백의 변화를 비교할 수 있습니다.
Unix Timestamp
import java.time.Instant;
public class Main {
public static void main(String[] args) {
Instant time = Instant.parse("2026-09-01T00:00:00Z");
long epochSeconds = time.getEpochSecond();
System.out.println(epochSeconds);
System.out.println(Instant.ofEpochSecond(epochSeconds));
}
}
예상 출력:
1788220800
2026-09-01T00:00:00Z
초와 밀리초를 혼동하면 날짜가 크게 어긋납니다. Unix Timestamp 변환기에서 단위를 명시해 교차 확인하세요.
직접 해보기
- Base64 원문을 자신의 문장으로 바꾸고 왕복 결과를 확인합니다.
- URL query를
Java 조건문으로 바꾸어 인코딩 결과를 비교합니다. getEpochSecond()를toEpochMilli()로 바꾸고 자릿수 차이를 기록합니다.- JSON의 마지막 쉼표를 일부러 추가해 Formatter가 어떤 오류를 알려주는지 확인합니다.
보안 확인
JWT, 비밀번호, API key, 실제 사용자 데이터는 연습 입력으로 사용하지 마세요. 예제용 가상 데이터만 사용합니다.
실무 데이터 처리는 형식과 경계를 함께 봅니다
파일, JSON, Base64, URL, 시간은 서로 다른 주제처럼 보이지만 공통점이 있습니다. 모두 프로그램 안의 값을 다른 경계로 옮기기 위한 표현 방식입니다. 파일은 디스크 경계, JSON은 시스템 간 데이터 교환 경계, Base64는 바이너리와 텍스트 경계, URL 인코딩은 주소 문법 경계, Timestamp는 시간대와 저장 형식 경계를 다룹니다. 경계를 넘을 때는 “어떤 형식으로 표현되는가”와 “받는 쪽이 어떤 단위를 기대하는가”를 반드시 확인해야 합니다.
예를 들어 문자열 "2026-09-01"은 사람이 보기에는 날짜지만, 프로그램 입장에서는 아직 텍스트입니다. LocalDate로 해석할지, UTC 자정의 Instant로 볼지, 한국 시간대의 시작으로 볼지에 따라 결과가 달라집니다. Base64 문자열도 보기에는 암호처럼 생겼지만 누구나 디코딩할 수 있는 텍스트 표현일 뿐입니다. JSON 문자열도 따옴표 하나가 빠지면 더 이상 유효한 데이터가 아닙니다.
실무에서 데이터 버그는 문법 자체보다 경계 계약을 잘못 이해해서 생기는 경우가 많습니다. API가 밀리초 Timestamp를 주는데 초로 해석하거나, URL 전체를 인코딩해서 https%3A%2F%2F... 형태로 만들어 버리거나, JSON에 마지막 쉼표를 넣어 파서가 실패하는 식입니다. 이 장의 예제는 작은 코드지만, 각 형식의 목적과 한계를 구분하는 훈련입니다.
파일 경로는 문자열보다 Path로 다루기
파일 이름은 문자열로 표현할 수 있지만, Java 코드에서는 Path를 사용하는 편이 안전합니다. Path는 운영체제별 구분자와 경로 조합을 다루는 표준 타입입니다. 문자열 덧셈으로 경로를 만들면 /와 \ 차이, 중복 구분자, 상대 경로 문제를 놓치기 쉽습니다.
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws Exception {
Path dataDir = Path.of("data");
Path scoreFile = dataDir.resolve("score.txt");
Files.createDirectories(dataDir);
Files.writeString(scoreFile, "Alex,1200");
System.out.println(Files.readString(scoreFile));
Files.deleteIfExists(scoreFile);
Files.deleteIfExists(dataDir);
}
}
resolve는 기준 경로 아래에 하위 경로를 붙입니다. 실제 서비스에서 사용자가 파일 이름을 입력한다면 더 조심해야 합니다. ../secret.txt 같은 상대 경로를 허용하면 의도한 폴더 밖의 파일에 접근할 수 있습니다. 파일 업로드나 다운로드 기능에서는 허용된 루트 디렉터리 안에 있는지 정규화 후 확인해야 합니다.
Path root = Path.of("uploads").toAbsolutePath().normalize();
Path requested = root.resolve(userInput).normalize();
if (!requested.startsWith(root)) {
throw new IllegalArgumentException("허용되지 않은 경로입니다.");
}
입문자는 이 코드의 모든 보안 맥락을 당장 외울 필요는 없습니다. 다만 파일 경로는 단순 문자열이 아니라 신뢰 경계라는 점을 기억하세요.
문자 인코딩을 명시하기
문자열을 바이트로 바꾸거나 바이트를 문자열로 읽을 때는 문자 인코딩이 필요합니다. Java의 편의 메서드는 UTF-8을 사용하는 경우가 많지만, 코드에서 의도를 분명히 하고 싶다면 StandardCharsets.UTF_8을 명시합니다. Base64 예제에서도 문자열을 바이트로 바꾸는 순간 UTF-8을 지정했습니다.
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) throws Exception {
Path path = Path.of("message.txt");
Files.writeString(path, "한글 메시지", StandardCharsets.UTF_8);
String text = Files.readString(path, StandardCharsets.UTF_8);
System.out.println(text);
Files.deleteIfExists(path);
}
}
인코딩이 맞지 않으면 글자가 깨지거나, 같은 파일을 다른 환경에서 읽을 때 결과가 달라질 수 있습니다. 웹과 대부분의 현대 시스템에서는 UTF-8이 표준처럼 쓰이지만, 오래된 CSV나 외부 기관 파일에서는 다른 인코딩이 등장할 수 있습니다. 파일을 읽기 전에 제공 문서의 인코딩 정보를 확인하는 습관이 좋습니다.
JSON을 직접 조립할 때의 위험
Java SE 표준 API에는 범용 JSON 파서가 없기 때문에, 이 장에서는 JSON 구조를 이해하는 데 집중합니다. 하지만 실제 프로젝트에서 JSON을 문자열 덧셈으로 직접 만드는 것은 위험합니다. 값 안에 큰따옴표, 줄바꿈, 역슬래시가 들어가면 이스케이프를 정확히 처리해야 합니다.
String name = "Alex \"Builder\"";
String json = "{\"name\":\"" + name + "\"}";
System.out.println(json);
이 코드는 의도한 JSON을 만들지 못합니다. 이름 안의 큰따옴표가 JSON 문법을 깨뜨리기 때문입니다. 실제 서비스에서는 Jackson, Gson 같은 검증된 JSON 라이브러리를 선택해 객체와 JSON 변환을 맡기는 것이 일반적입니다. 라이브러리를 쓰더라도 JSON의 기본 구조를 알아야 오류 메시지를 이해할 수 있습니다.
JSON에서 자주 틀리는 부분은 다음과 같습니다.
- key는 작은따옴표가 아니라 큰따옴표를 사용합니다.
- 문자열 값도 큰따옴표를 사용합니다.
- 마지막 항목 뒤에는 쉼표를 붙이지 않습니다.
- 숫자와 문자열 숫자는 다릅니다.
- 날짜는 표준 타입이 아니라 보통 문자열 규칙으로 주고받습니다.
ToolPado JSON Formatter로 연습할 때는 예제용 데이터만 사용하세요. 운영 토큰, 개인정보, 고객 데이터는 브라우저 안에서 처리되는 도구라도 복사하지 않는 습관이 안전합니다.
Base64를 사용하는 상황과 피해야 할 상황
Base64는 바이너리 데이터를 텍스트만 허용하는 경로로 옮길 때 사용합니다. 예를 들어 작은 이미지, 파일 조각, 인증 헤더 일부, 이메일 첨부 표현 등에서 만날 수 있습니다. 원래 바이트보다 길이가 늘어나므로 대용량 데이터를 무작정 Base64 문자열로 들고 다니는 것은 비효율적입니다.
Base64가 암호화가 아니라는 점은 여러 번 강조할 가치가 있습니다. 다음 코드는 누구나 되돌릴 수 있습니다.
String encoded = Base64.getEncoder().encodeToString("password123".getBytes(StandardCharsets.UTF_8));
String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8);
System.out.println(decoded);
민감 정보를 보호하려면 암호화, 해시, 접근 제어, 비밀 관리 같은 별도 보안 기술이 필요합니다. Base64는 표현 형식일 뿐입니다. 또한 URL 안에 Base64 값을 넣을 때는 +, /, = 문자가 문제가 될 수 있어 URL-safe Base64 인코더를 고려해야 합니다.
String token = Base64.getUrlEncoder().encodeToString("voxel-map".getBytes(StandardCharsets.UTF_8));
System.out.println(token);
일반 Base64와 URL-safe Base64는 목적이 다르므로 받는 쪽 API가 어떤 형식을 요구하는지 확인하세요.
URL 인코딩은 부분에 적용합니다
URL 인코딩에서 흔한 실수는 전체 URL을 한 번에 인코딩하는 것입니다. 보통 인코딩해야 하는 것은 query parameter의 값입니다. 주소의 https://, /, ?, &, = 같은 구분자까지 모두 인코딩하면 브라우저나 서버가 URL 구조를 알아볼 수 없습니다.
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) {
String keyword = "Java 조건문";
String url = "https://example.com/search?q="
+ URLEncoder.encode(keyword, StandardCharsets.UTF_8);
System.out.println(url);
}
}
공백이 +로 바뀌는 것은 HTML form query 규칙입니다. 경로 조각에서는 %20이 더 적절할 수 있습니다. URL의 어느 부분을 다루는지에 따라 규칙이 조금씩 다르므로, 표준 API와 프레임워크가 제공하는 URI 빌더를 쓰는 편이 안전합니다.
시간은 Instant와 지역 날짜를 구분하기
Instant는 시간대와 무관한 한 순간을 UTC 기준으로 표현합니다. 반면 LocalDate는 시간대 없는 달력 날짜입니다. “2026년 9월 1일”이라는 날짜를 한국 시간대의 시작으로 볼지, UTC의 시작으로 볼지에 따라 epoch 값이 달라질 수 있습니다.
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
public class Main {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2026, 9, 1);
Instant seoulStart = date.atStartOfDay(ZoneId.of("Asia/Seoul")).toInstant();
Instant utcStart = date.atStartOfDay(ZoneId.of("UTC")).toInstant();
System.out.println(seoulStart.getEpochSecond());
System.out.println(utcStart.getEpochSecond());
}
}
같은 날짜라도 시간대를 붙여 순간으로 바꾸면 epoch second가 달라집니다. API 문서가 “UTC timestamp”를 요구하는지, 사용자 지역 날짜를 요구하는지 확인해야 합니다. 금융, 예약, 로그 분석처럼 시간 정확도가 중요한 기능에서는 이 차이가 실제 장애로 이어질 수 있습니다.
데이터 도구를 검증 흐름에 넣기
ToolPado의 개발자 도구들은 학습용 확인에 유용합니다. Java에서 만든 Base64 결과를 Base64 변환기로 디코딩해 보고, URL 인코딩 결과를 URL 인코더에서 비교하고, epoch 값을 Unix Timestamp 변환기에 넣어 날짜가 맞는지 확인할 수 있습니다. 중요한 것은 도구 결과를 맹신하는 것이 아니라, 코드와 도구가 같은 계약을 해석하고 있는지 교차 확인하는 것입니다.
검증 순서는 다음처럼 잡을 수 있습니다.
- 예제 입력과 기대 출력을 먼저 적습니다.
- Java 코드로 변환 결과를 만듭니다.
- 도구에 같은 입력을 넣어 결과를 비교합니다.
- 단위, 인코딩, 시간대가 표시되어 있는지 확인합니다.
- 민감한 실제 데이터는 사용하지 않습니다.
이 습관은 나중에 API 연동을 디버깅할 때도 그대로 이어집니다.
이번 장의 정리
실무 데이터 처리는 형식의 목적을 구분하는 일에서 시작합니다. 파일은 Path와 문자 인코딩을 의식하고, JSON은 구조와 이스케이프 규칙을 이해하되 실제 파싱은 검증된 라이브러리에 맡깁니다. Base64는 암호화가 아니라 텍스트 표현이고, URL 인코딩은 전체 주소가 아니라 필요한 부분에 적용합니다. 시간 데이터는 초와 밀리초, UTC와 지역 시간대를 분리해서 생각해야 합니다. 형식과 경계를 정확히 읽는 습관이 예외 처리보다 앞선 첫 번째 안전장치입니다.