개발/TIL

spring boot 애플리케이션에서 null 데이터 처리하기

ebang 2025. 2. 22. 22:59

프로그래밍을 할 때 Null 데이터를 잘 처리하는 것은 매우 중요하다. 

 

프레임워크 혹은 라이브러리를 활용해 Null 을 허용하지 않음으로써 null에 접근하는 오류를 사전에 방지할 수 있다. 다음 라이브러리에서 어노테이션 활용이 가능하다.

 

Null 처리를 돕는 몇가지 라이브러리에 대해 정리해보려고 한다. 

Javax Validation (JSR-303/JSR-380)  @NotNull  null을 허용하지 않음
Lombok (lombok.NonNull) @NonNull null이 들어오면 NullPointerException 발생
Spring (org.springframework.lang) @NonNull null을 허용하지 않음 (Spring 전용)

 

1. Javax Validation (javax.validation.constraints)

  • 대표적인 어노테이션: @NotNull, @NotNull, @Size, @Pattern, @Min, @Max
  • JSR-303 (Bean Validation 1.0), JSR-380 (Bean Validation 2.0)에서 표준화 되어서 사용된다. 
  • 런타임 시점에 검증이 수행되며, 검증 실패 시 예외 발생 (MethodArgumentNotValidException)이 일어난다. 
  • 유효성 검사를 위해 Spring@Valid 또는 @Validated와 함께 사용한다. 
  • Spring과 Hibernate Validator를 함께 사용하여 Controller에서 요청 값을 검증할 수 있다. : MVC 요청 바디 검증시 활용 (DTO 필드 검증)
  • 메서드 파라미터를 검증하는데는 한계가 있다.

 

2. Lombok (lombok.NonNull)

  • 대표적인 어노테이션: @NonNull
  • Lombok이 자동으로 null 체크를 추가하여, 런타임에서 NullPointerException을 발생시킨다. 
  • 따라서 컴파일 타임이 아니라 런타임에서 검증이 이루어진다.
  • Spring Validation과 달리 HTTP 요청 검증보다는 객체의 생성자나 메서드 호출 시 검증하는 용도로 사용된다.
  • null 관련된 부분만 검증할 수 있다.

🔹 예제

import lombok.NonNull;

public class User {
    private String name;

    public User(@NonNull String name) { // name 이 null이면 NullPointerException 발생
        this.name = name;
    }
}

@NonNull을 사용하면, 내부적으로 다음과 같이 변환된다. 

public User(String name) {
    if (name == null) {
        throw new NullPointerException("name is marked non-null but is null");
    }
    this.name = name;
}

 

 

3. Spring (org.springframework.lang)

  • 대표적인 어노테이션: @NonNull, @Nullable
  • Spring 5부터 도입된 어노테이션으로, Spring 내부에서 null 체크를 보조하는 용도로 사용된다. 
  • JSR-303 기반의 @Valid, @Validated 외에도 커스텀한 검증 로직을 추가할 수 있다. 
  • Spring 에서 제공하는 만큼, Spring MVC 와 연동이 강하며 DTO 필드를 검증할때 유용하다. 서비스 계층을 검증할 때도 사용할 수 있다. 
  • 커스텀한 검증 로직을 사용하는 경우, Validator 를 implement 해서 구현해야한다. 

예제

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

@Component
public class UserValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return UserDto.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        UserDto userDto = (UserDto) target;

        if (userDto.getPassword() != null && !userDto.getPassword().equals(userDto.getConfirmPassword())) {
            errors.rejectValue("confirmPassword", "Passwords do not match", "비밀번호가 일치하지 않습니다.");
        }
    }
}


@RestController
@RequestMapping("/users")
public class UserController {

    private final UserValidator userValidator;

    public UserController(UserValidator userValidator) {
        this.userValidator = userValidator;
    }

    @PostMapping
    public ResponseEntity<String> createUser(@RequestBody UserDto userDto, Errors errors) {
        userValidator.validate(userDto, errors);

        if (errors.hasErrors()) {
            return ResponseEntity.badRequest().body(errors.getAllErrors().toString());
        }

        return ResponseEntity.ok("User created successfully");
    }
}

@NonNull, @Nullable 예제 

import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

public class SpringExample {
    public void process(@NonNull String input) {
        // input이 null이면 IDE에서 경고
    }

    public void display(@Nullable String message) {
        // message는 null 가능
    }
}