JSpecify
java의 null 표준명세
JSpecify
JSpecify는 Java에서 Null 안전성을 보장하기 위한 표준 명세입니다.
기존의 @Nullable, @NotNull 같은 애너테이션이 라이브러리마다 다르게 동작하는 문제를 해결하고, 일관된 Null 처리 방식을 제공하기 위해 개발되었습니다.
또한 스프링 7.0 부터 적용되고 Spring 내부에서 사용되는 스프링자체 null-safety 애노테이션은 사용되지 않을 예정이다.
JSpecify가 필요한 이유
- Null 관련 애너테이션의 표준 부재
- Spring, JetBrains, Checker Framework 등에서 각자 @Nullable, @NotNull을 정의해 사용
- 라이브러리 간 호환성이 떨어지고 혼란 발생
- 정적 분석 도구 지원 부족
- 컴파일러나 IDE가 Null 안전성을 보장하기 어려움
- Java가 Kotlin의 Null 안전성만큼 강력한 지원을 제공하지 않음
표준화된 annotation
@Nullable: 해당 타입이 널일 수 있음 (ex. @Nullable String)@NonNull: 해당 타입이 절대 널이 아님 (ex. @NonNull String)@NullMarked: 해당 범위(모듈, 패키지, 클래스, 메서드)에 선언된 타입들은 기본적으로 널이 아닐 것으로 간주@NullUnmarked: 이미 @NullMarked가 선언된 범위 안에서 다시 널에 대한 지정 없음 상태로 되돌리기
Type-use Annotation Syntax
JSpecify가 제시하는 널 애노테이션(@Nullable, @NonNull)은 type-use 위치에서 적용할 수 있습니다.
이는 자바 8부터 도입된 Type Annotations개념을 활용하는 것으로, 어떤 타입에 @Nullable을 붙이느냐에 따라 의미가 달라질 수 있다는 것을 의미합니다.
- @Nullable String[]
- 배열 요소(String)가 null일 수 있다는 것을 의미
- 배열 객체 자체는 @NullMarked 하에서 NonNull로 간주됨
- String @Nullable []
- 배열 객체 자체가 null일 수 있다는 의미
- 배열 안의 String 요소는 NonNull
- @Nullable String @Nullable []
- 배열 객체도 null 가능 + 배열 안의 요소도 null 가능
- 가장 넓은 범위로 null을 허용
사용 예시
@Nullable
@Nullable을 사용하면 해당 변수나 메서드 파라미터가 null일 수 있음을 명시합니다.
1
2
3
4
5
6
7
8
9
10
import org.jspecify.annotations.Nullable;
public class UserService {
public String getUserName(@Nullable String userId) {
if (userId == null) {
return "Unknown User";
}
return "User: " + userId;
}
}
- userId가 null일 수 있음을 명시
- null 체크 후 처리가 필요
@NonNull
@NonNull을 사용하면 해당 변수나 메서드 파라미터가 절대 null일 수 없다는 것을 보장합니다.
1
2
3
4
5
6
7
import org.jspecify.annotations.NonNull;
public class UserService {
public String getUserInfo(@NonNull String userId) {
return "User Info: " + userId;
}
}
- userId는 절대 null이 아니어야 하므로 호출 시 반드시 유효한 값이 전달되어야 함
@NullMarked
@NullMarked는 패키지, 클래스, 또는 메서드에 적용되어 해당 범위 내에서 기본적으로 모든 변수는 null이 아님을 명시합니다.
1
2
3
4
5
6
7
8
9
10
11
12
@NullMarked
package com.example;
public class UserService {
public String getUserName(String userId) { // userId는 기본적으로 NonNull
return "User: " + userId;
}
public String getUserInfo(String userId) { // userId는 기본적으로 NonNull
return "User Info: " + userId;
}
}
- @NullMarked 적용 시, 모든 파라미터가 기본적으로 NonNull으로 간주
- @Nullable을 명시적으로 지정하여 null을 허용할 수 있음
@NullUnmarked
@NullUnmarked는 @NullMarked가 적용된 범위 내에서 다시 null 값을 허용하는 역할을 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@NullMarked
package com.example;
import org.jspecify.annotations.NullUnmarked;
public class UserService {
public String getUserName(String userId) { // userId는 NonNull로 간주
return "User: " + userId;
}
@NullUnmarked
public String getOptionalUserInfo(String userId) { // userId는 Nullable로 간주
if (userId == null) {
return "User Info is not available.";
}
return "User Info: " + userId;
}
}
- @NullUnmarked는 @NullMarked 범위에서 null을 허용할 수 있도록 되돌리는 역할
- getOptionalUserInfo에서는 userId가 null일 수 있으므로 @Nullable처럼 처리
JSpecify와 기존 @Nullable 애너테이션 비교
| 기준 | JSpecify | JetBrains | Spring |
|---|---|---|---|
| 표준화 여부 | ✅ 공식 표준화 진행 중 | ❌ JetBrains 한정 | ❌ Spring 전용 |
패키지 적용 (@NullMarked) | ✅ 지원 | ❌ 지원 안 함 | ❌ 지원 안 함 |
| 정적 분석 지원 | ✅ 강력한 지원 (IDE, Linter) | 🔹 일부 지원 | 🔹 일부 지원 |
| Kotlin 상호운용성 | ✅ 공식 지원 목표 | 🔹 일부 지원 | ❌ 거의 없음 |
| 컴파일 타임 검사 | ✅ 가능 | 🔹 일부 가능 | ❌ 거의 없음 |
| 런타임 검사 | ❌ 불가능 | ❌ 불가능 | ❌ 불가능 |
JSpecify (org.jspecify.annotations.Nullable)
- Java의 공식적인 Null 애너테이션 표준을 목표로 개발됨
@NullMarked,@NullUnmarked를 지원하여 패키지 단위로 Null 규칙 적용 가능- 정적 분석 도구(컴파일러, IDE)와의 호환성 최적화
- Kotlin과의 상호운용성을 고려한 설계
JetBrains (org.jetbrains.annotations.Nullable)
- Kotlin 및 IntelliJ IDEA에서 널리 사용
- IntelliJ에서 코드 분석 기능 제공 (
@NotNull,@Nullable기반으로 경고 표시) - Java 공식 표준이 아니므로 다른 도구와의 호환성이 제한적
Spring (org.springframework.lang.Nullable)
- Spring Framework 전용 애너테이션
- 런타임에 동작하는 기능은 없으며, Spring 내부 코드에서만 사용됨
- IDE나 정적 분석 도구에서 활용도가 낮음
장점
- 일관된 Null 안전성 제공
- 기존 @Nullable, @NotNull 애너테이션이 라이브러리마다 달라 발생하는 문제 해결
- 정적 분석 도구(컴파일러, IDE)와 호환 가능
- Kotlin과의 상호운용성 강화
- Kotlin의 Null 안전성 시스템과 함께 사용할 수 있도록 설계
- Java -> Kotlin 변환 시 명확한 Null 처리 가능
- 점진적 도입 가능
- @NullMarked, @NullUnmarked를 활용해 기존 코드와 혼용 가능
- 레거시 코드에서 강제 적용 없이 일부 클래스에서만 사용 가능
JSpecify 도입 시 고려할 점
아직 정식 Java 표준이 아님현재 Java 표준 라이브러리에는 포함되지 않음-> 스프링 7.0 부터 표준이 될 예정JSR(Java Specification Request)로 공식 채택될 가능성이 있지만, 아직 확정되지 않음-> 확정됨
- 런타임이 아닌 정적 분석 용도
- @Nullable이 컴파일 타임에서만 검증되며, 런타임에는 동작하지 않음
- 실행 중 NullPointerException(NPE)이 발생할 수 있으므로, 반드시 명시적 검증 필요
정리
JSpecify는 자바 진영의 nullability 의미를 통일해 정적 분석 품질과 Kotlin 상호운용성을 높이려는 시도입니다. 다만 런타임 보호 장치가 아니라는 점은 그대로이므로, 실제 NPE 방지는 명시적 검증과 함께 가져가야 합니다.
