Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 stream - how to properly make NPE-safe stream

Last week got very strange NPE in a stream that caused my lots of troubles so right now I'm feeling like being over-safe with NPE when using a Stream.

Here is my method right now:

private boolean matchSomeError(final List<ErrorAtMessageLevel> errorList) {
    return errorList.stream()
        .filter(errorAtMessageLevel -> errorAtMessageLevel.getErrorSegment() != null && errorAtMessageLevel.getErrorSegment().getErrorDetails() != null)
        .map(errorAtMessageLevel -> errorAtMessageLevel.getErrorSegment().getErrorDetails())
        .anyMatch(errorDetails -> SOME_FANCY_ERROR_CODE.equals(errorDetails.getErrorCode()));
}

My problem is that I'm dealing with external POJO here so I cannot change it and make it null-safe so I have to adjust my code.

Here are some restrictions: 1) errorList - cannot be null here so a call to .stream() is safe - when it's empty it will just return false 2) getErrorSegment() and getErrorDetails() can both be nulls tha's why I'm using filter like that to make sure none of them is null 3) getErrorCode() can be null but it will never throw NPE because it will just return false when matching with null - fine by me.

How would you go about making that stream better? I feel like my .filter() is bad and it could be made better. Writing lots of code like that lately because I'm not sure anymore how the stream is working with nulls and don't want to get NPE in .map() because it's called on null

like image 265
ohwelppp Avatar asked Dec 24 '22 03:12

ohwelppp


2 Answers

You could filter the nulls out more elegantly this way:

private boolean matchSomeError(final List<ErrorAtMessageLevel> errorList) {
    return errorList.stream()
        .map(ErrorAtMessageLevel::getErrorSegment)
        .filter(Objects:nonNull)
        .map(ErrorSegment::getErrorDetails)
        .filter(Objects:nonNull)
        .anyMatch(errorDetails -> SOME_FANCY_ERROR_CODE.equals(errorDetails.getErrorCode()));
}
like image 191
Eran Avatar answered Jan 08 '23 11:01

Eran


I think the nicest way to write this is to define a separate method which uses an Optional to avoid the nulls. You can then refer to this with a method reference in your stream:

return errorList.stream()
    .map(MyClass::getErrorCode)
    .flatMap(Optional::stream) // from Java 9, else filter then map to Optional.get
    .anyMatch(SOME_FANCY_ERROR_CODE::equals);


private static Optional<String> getErrorCode(final ErrorAtMessageLevel error)
{
    return Optional.ofNullable(error)
        .map(ErrorAtMessageLevel::getErrorSegment)
        .map(ErrorSegment::getErrorDetails)
        .map(ErrorDetails::getErrorCode);
}

Your class names may be different for the method references. Your code didn't show what classes were used, so I just used sensible guesses.

like image 33
Michael Avatar answered Jan 08 '23 09:01

Michael