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)이 발생할 수 있으므로, 반드시 명시적 검증 필요
[출처]
- https://jspecify.dev/
This post is licensed under CC BY 4.0 by the author.