Side stepping that you're using a runtime NullPointerException as an example, Java's checked exceptions are just not particularly good at what you want to do.
If you have a function that has an abnormal result as its API, it should have that as part of its return value, because returning as values is what you do with results. Checked exceptions in contrast don't compose. See for example this code:
X myFunction(Y param) throws MyFailureException();
List transformed = list.stream()
.map(e -> myFunction(e))
.collect(toList());
This is not allowed, because a the lambda in map can't throw. Even if that was allowed map would need a generic "throws Exception" as its API, which would be awful. With checked exceptions the only possibilities you have is catch the exception locally at the point of calling the function or stop as soon as you encounter the failure and bubble it up.
Instead, if you make the part of the return value you can do whatever. I'll use Vavr Try as an example, but you can also do this in Java 17 with sealed interfaces or in a miriad other ways.
Try myFunction(Y param);
List> transformed = list.stream()
.map(e -> myFunction(e))
.collect(toList());
Now you can still handle failure locally, but you can also check the whole list and make a decision based on that. Or you can propagate the results including failures because this is not the best place to handle them. That's what I mean with composes vs not composes.