왜 우리 서비스는 자바 21버전과 스프링 프레임워크4.0 버전을 선택했는가?

2026. 8. 4. 18:01·운영 & 인프라

우테코 최종 프로젝트인 '링아웃'팀의 로고다.

 

이 글은 openjdk와 spring-boot wiki의 내용을 참고하여 작성한 글입니다.

 

 

새 프로젝트를 시작할 때 가장 먼저 부딪히는 건 코드가 아니라 선택이다. 자바는 몇 버전을 쓸지, 스프링 부트는 어디에 맞출지, 폴더는 어떻게 나눌지. 늘 하던 대로 최신 버전 골라놓고 넘어가도 되지만, 그렇게 정한 결정은 나중에 "이거 왜 이렇게 했더라?"라는 질문에 답을 못 한다.

그래서 이번엔 개발 방향과 그 이유를 담은 설계서를 먼저 썼다. 아래는 그 문서를 정리한 내용이다.

 

1. 자바 버전

자바 버전을 고려할 때, 실제 자바 개발자들이 작성하는 문서인 openjdk 문서를 참조하자. 이전 LTS 버전과의 변경점과 최신 버전의 java 변경 사항을 구체적으로 확인할 수 있다.

 

LTS만 본다

버전을 고를 때 전체 목록을 다 볼 필요는 없다. 우리가 봐야 하는 건 LTS(Long Term Support)를 지원하는 버전들이다. 그 사이 버전들은 6개월 주기로 배포되고 더 이상 보안 및 신규 패치를 지원하지 않는 징검다리 버전이다.

따라서 자바 8, 11, 17, 21, 25만 확인한다.(8은 비교적 오래된 버전이라 제외)

 

 

11 → 17

OpenJDK 릴리스 노트를 기준으로 봤을 때, 11에서 17로 넘어오며 실질적으로 체감되는 변화는 이 정도였다.

 

- CPU/OS 환경

17버전부터 Apple Silicon 네이티브 지원이 들어왔다. M 시리즈 맥을 쓴다면 사실상 17이 하한선이다.

- Garbage Collectors — 11→17에서 가장 큰 실질적 변화

저지연 GC가 들어와서 힙이 커도 멈춤 시간이 짧다. 그리고 기본 GC인 G1이 지속적으로 개선됐는데, 이건 컨테이너 환경에서 메모리 비용에 직결되는 부분이다.

- Run-Time System

Class Data Sharing으로 클래스 메타데이터를 미리 저장해둬서 JVM 시작 시간을 단축한다. Spring Boot처럼 클래스가 많은 앱에서 효과가 크다.

- Flight Recorder

JFR은 JVM 내부 이벤트(GC, 스레드, 메모리)를 기록하는 프로파일링 도구다. 원래는 덤프해서 파일로 열어봐야 했는데, JFR Event Streaming이 들어오면서 실시간 모니터링이 가능해졌다.

- Language

  • Records — record 타입의 불변 데이터 클래스. DTO 작성이 완전히 달라진다.
  • Text Block — """ 3중 따옴표로 여러 줄 문자열. JSON이나 SQL 쓸 때 편리하다.

 

 

17 → 21

- 21버전으로의 패치는 Virtual Threads 도입이 다라고 말해도 과언이 아니다.

 

기존 자바 스레드는 OS 스레드와 1:1로 묶여 있었다. OS 스레드는 수천 개가 한계다. 가상 스레드는 JVM이 관리하는 경량 스레드라 수백만 개를 만들 수 있고, 블로킹되면 JVM이 알아서 캐리어 스레드에서 떼어낸다. 결국 가상 스레드는 OS 캐리어 스레드가 대기하지 않고 계속 갈아타게 만들어서 사용률을 높이는 것이다.

 

그리고 Spring Boot 3.2+에서는 이걸 한 줄로 켤 수 있다.

spring.threads.virtual.enabled=true

언어 차원에서는 sealed 키워드로 상속을 제어할 수 있게 됐다. 기존에는 상속 제어 수단이 final 밖에 없었다.

 

public sealed interface Shape permits Circle, Rectangle, Triangle {}

public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Triangle(double base, double height) implements Shape {}

무엇보다 17에서 Remove된 것들이 없어서 버전 호환이 깨지지 않는다. 마이그레이션 관점에서는 이게 제일 크다.

 

 

21 → 25

25도 매력적인 카드다.

  • 클래스 로딩 결과와 메서드 프로파일을 캐시로 저장해두고 실제 실행 때 재사용해서, 스프링 부트처럼 클래스가 수천 개인 앱에서 기동 시간이 30~40% 줄어든다는 보고가 있다.
  • 자바 객체 헤더를 줄여서 힙 사용량이 줄어든다. 작은 객체를 많이 만드는 앱에서 힙 10~20% 절감 사례가 있다.
  • synchronized 키워드가 붙은 작업에 가상 스레드가 21버전에서는 다른 가상 스레드로 변경될 수 없는 pinning 이슈가 있었는데, 이 부분이 25버전이 되면서 synchronized 의 구현이 변경되면서 해결되었다.

다만 이건 "가상 스레드를 실제로 쓸 계획이 있다면" 얹어볼 만한 이점이다. 빠르게 만들어야 하고 삽질할 여유가 없다면 21이 낫다.

 

 

결론: 21

우리 서비스는 '알라미' 앱 같은 행동 기반 알람인 알람 앱이다.

알람 도메인에 커뮤니티 기능이 확장된 앱을 지향하므로, 대량의 알림을 발송할 일이 있고 이 부분에서 외부(FCM/APNS)에 병목이 발생할 가능성이 있다고 판단했고 가상 스레드를 충분히 도입할만한 부분이라고 판단하였다.

예를들면, 알림 앱을 주로 사용하게 되는 아침 시간에 같은 챌린지를 하는 친구에게 '콕 찌르기' 같은 푸시 알림이 특정 시간대에 몰릴 가능성이 충분하다고 판단했다.

 

팀 프로젝트는 기간이 정해져 있고, 새 버전에서 터지는 문제를 붙잡고 있을 여유가 없다. 21은 17과 호환이 깨지지 않으면서 가상 스레드라는 확실한 카드를 준다. 그래서 21로 간다.

 

 

 

2. 스프링 부트 버전

자바 21을 골랐으니, 가상 스레드를 한 줄로 켤 수 있는 3.2 이상이 하한선이다. 그다음은 상한선을 어디에 둘지의 문제다.

 

- 3.2

가상 스레드 지원과 RestClient가 들어온 버전이다. RestClient는 기존 RestTemplate 대비 장점이 있다. 다만 3.2.0은 2023년 11월 릴리스라 OSS 지원이 이미 끝났다. (*OSS 지원은 누구나 무료로 받는 지원으로, 심각한 버그와 보안 이슈에 대한 수정이 제공된다.) 스프링 부트는 OSS 기준 마이너 버전을 12개월, 메이저를 릴리스로부터 최소 3년 지원한다.

 

- 내가 기존 프로젝트에서 쓰던 3.5

여기서 한 번 더 확인할 게 있다. 3.5는 3.x의 마지막 마이너 버전인데, 2026년 6월 30일부로 OSS 지원이 종료됐다. 익숙하다는 이유로 3.5를 고르면 시작하자마자 보안 패치를 못 받는 버전 위에 올라가는 셈이다.

 

- 4.0 / 4.1

4.0은 2025년 11월에 나온 메이저 버전이고, 4.1이 2026년 6월에 나왔다. 지금 OSS 지원을 받는 라인은 이 둘뿐이다.

메이저 버전인 만큼 바뀐 게 많은데, 눈에 들어온 건 이 정도다.

  • API 버저닝 — 빈으로 세밀하게 API 버저닝을 할 수 있다. 그동안 URL에 /v1을 박거나 직접 구현하던 걸 프레임워크가 흡수했다.
  • @SoftDelete 개선 — 컬럼 값에 true/false/null만 저장할 수 있던 것에서 TIMESTAMP 형식으로 삭제 시간을 저장할 수 있게 됐다. 그동안 불편했던 부분이라 사용성이 확 올라갔다.
  • 자바 25를 정식으로 지원하면서 자바 17 호환성도 유지한다. 즉 21로 시작해도 문제가 없고, 나중에 25로 올릴 여지도 남는다.

 

OSS 지원 기간이 많이 남은점(25.12월 배포)과 새로운 유용한 기능들이 추가된 점을 고려하면 스프링 부트 4.0 버전은 가장 매력적인 선택지다.

 

 

메이저 버전은 Spring Framework 7 기반이라 마이그레이션 부담이 있는 게 사실이다. 하지만 그건 "기존 프로젝트를 올릴 때"의 이야기고, 우리는 새로 시작한다. 지원이 끝난 버전에서 출발해서 프로젝트 도중에 올리는 것보다, 지금 지원되는 라인에서 시작하는 게 맞다고 봤다.

 

 

 

마무리

기술 스택을 고르는 일은 결국 "무엇을 포기할 것인가" 트레이드오프를 정하는 일이었다. 최신 버전의 이점을 가져가면 안정성과 자료의 양을 일부 포기해야 하고, 좋은 구조를 가져가면 개발 속도를 포기해야 한다.

중요한 건 그 트레이드오프를 알고 골랐느냐다. 다음에 팀원이 "왜 21이에요?"라고 물었을 때 "최신이라서요"가 아니라 "17 호환이 안 깨지면서 가상 스레드를 쓸 수 있어서요"라고 답할 수 있다면, 이 문서는 제 몫을 한 거라고 생각한다.

'운영 & 인프라' 카테고리의 다른 글

클라우드와치로 로깅 시스템을 구축한 후 해결할 수 있었던 문제들  (0) 2026.07.27
'운영 & 인프라' 카테고리의 다른 글
  • 클라우드와치로 로깅 시스템을 구축한 후 해결할 수 있었던 문제들
dev_leejaehyeok97
dev_leejaehyeok97
매일매일 공부하는 개발자 이재혁의 블로그입니다.
  • dev_leejaehyeok97
    개발자 이재혁님의 블로그
    dev_leejaehyeok97
  • 전체
    오늘
    어제
    • 분류 전체보기 (42)
      • 운영 & 인프라 (2)
      • JPA & Hibernate (1)
      • 운영체제 (0)
      • 네트워크 (5)
      • DB (9)
      • 코딩테스트 (0)
      • 아키텍처 (7)
      • 회고 (1)
      • 언어(java, kotlin, python ..... (6)
      • 우아한 테크코스 프리코스 (4)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    분산락
    redis
    Using temporary
    좋아요api
    MySQL
    filesort
    정렬방식
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
dev_leejaehyeok97
왜 우리 서비스는 자바 21버전과 스프링 프레임워크4.0 버전을 선택했는가?
상단으로

티스토리툴바