Post

JSpecify

java의 null 표준명세

JSpecify

JSpecify는 Java에서 Null 안전성을 보장하기 위한 표준 명세입니다.

기존의 @Nullable, @NotNull 같은 애너테이션이 라이브러리마다 다르게 동작하는 문제를 해결하고, 일관된 Null 처리 방식을 제공하기 위해 개발되었습니다.

또한 스프링 7.0 부터 적용되고 Spring 내부에서 사용되는 스프링자체 null-safety 애노테이션은 사용되지 않을 예정이다.

JSpecify가 필요한 이유

  1. Null 관련 애너테이션의 표준 부재
    • Spring, JetBrains, Checker Framework 등에서 각자 @Nullable, @NotNull을 정의해 사용
    • 라이브러리 간 호환성이 떨어지고 혼란 발생
  2. 정적 분석 도구 지원 부족
    • 컴파일러나 IDE가 Null 안전성을 보장하기 어려움
  3. 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을 붙이느냐에 따라 의미가 달라질 수 있다는 것을 의미합니다.

  1. @Nullable String[]
    • 배열 요소(String)가 null일 수 있다는 것을 의미
    • 배열 객체 자체는 @NullMarked 하에서 NonNull로 간주됨
  2. String @Nullable []
    • 배열 객체 자체가 null일 수 있다는 의미
    • 배열 안의 String 요소는 NonNull
  3. @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 애너테이션 비교

기준JSpecifyJetBrainsSpring
표준화 여부✅ 공식 표준화 진행 중❌ 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나 정적 분석 도구에서 활용도가 낮음

장점

  1. 일관된 Null 안전성 제공
    • 기존 @Nullable, @NotNull 애너테이션이 라이브러리마다 달라 발생하는 문제 해결
    • 정적 분석 도구(컴파일러, IDE)와 호환 가능
  2. Kotlin과의 상호운용성 강화
    • Kotlin의 Null 안전성 시스템과 함께 사용할 수 있도록 설계
    • Java -> Kotlin 변환 시 명확한 Null 처리 가능
  3. 점진적 도입 가능
    • @NullMarked, @NullUnmarked를 활용해 기존 코드와 혼용 가능
    • 레거시 코드에서 강제 적용 없이 일부 클래스에서만 사용 가능

JSpecify 도입 시 고려할 점

  1. 아직 정식 Java 표준이 아님
    • 현재 Java 표준 라이브러리에는 포함되지 않음 -> 스프링 7.0 부터 표준이 될 예정
    • JSR(Java Specification Request)로 공식 채택될 가능성이 있지만, 아직 확정되지 않음 -> 확정됨
  2. 런타임이 아닌 정적 분석 용도
    • @Nullable이 컴파일 타임에서만 검증되며, 런타임에는 동작하지 않음
    • 실행 중 NullPointerException(NPE)이 발생할 수 있으므로, 반드시 명시적 검증 필요

[출처]

  • https://jspecify.dev/
This post is licensed under CC BY 4.0 by the author.