1

Let's say we have a class Product which has some nullable fields that are allowed to be null, for example, quantityAvailable and quantityReserved.

I may want to validate these fields and throw an error, but since they are allowed to be null, I have, firstly, to check for null:

if (product.getQuantityAvailable() != null && product.getQuantityReserved() != null) {
  if (product.getQuantityAvailable() < product.getQuantityReserved()) {
    System.out.println("Error");
  }
}

When you have many nullable fields and many null-checks, this becomes tedious. Even with ofNullable it doesn't get any better.

Optional.ofNullable(product.getQuantityAvailable())
    .ifPresent((available) -> {
          Optional.ofNullable(product.getQuantityReserved())
              .ifPresent((reserved) -> {
                    if (available < reserved) {
                      System.out.println("Error");
                    }
                  });
        });

Ideally there would exist some annotation that would check for nulls and skip if any were found, but obviously this wouldn't work.

if (@Nullable product.getQuantityAvailable() < @Nullable product.getQuantityReserved()) {
  System.out.println("Error");
}

Is there a solution to avoid the boilerplate above?

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
Rosa
  • 39
  • 5
  • Here is a nice article that explains what you can do to avoid null checks: https://www.baeldung.com/java-avoid-null-check – bezbos. Aug 12 '22 at 11:29
  • @AlexanderIvanchenko there are no `Optional` fields here. I meant "optional" as in "not required", not the Java class. I explain this at the top of my post. The problem here is that some fields in my model are not required, and can be null in the database. – Rosa Aug 12 '22 at 12:40
  • @BoškoBezik Hey I came across that article while searching but unfortunately that's for a different case where you want a value to actually not be null, whereas in my case null is allowed. – Rosa Aug 12 '22 at 12:44
  • @AlexanderIvanchenko You said "you make these fields `Optional` in the first place" in your other comment, not me. I never said I had `Optional` fields. This was something you said. – Rosa Aug 12 '22 at 12:46
  • Your best option might be to use a language that has null-safety built in, such as Kotlin. That would allow you to write `product?.let { quantityAvailable > quantityRequired }`, which still has the same null check it's just easier to read and write. – Jorn Aug 12 '22 at 12:57
  • @Rosa well in that case there is not much you can do. You will have to do a bunch of null checks. – bezbos. Aug 12 '22 at 13:39
  • @Rosa The title of the question and some phrases in it are confusing. It has to be **Nullable** instead of *Optional*. The phrase `optional properties` makes sense only in JavaScript, **not** in Java. – Alexander Ivanchenko Aug 12 '22 at 14:09

3 Answers3

2

Firstly, there's nothing wrong with explicit null-checks.

Secondly, Optional.ofNullable() is not meant for validation. By hiding a null-check with it, you're obscuring what the code does and making it harder to read.

Here's a quote from the answer by @StuartMarks, Java and OpenJDK developer, regarding what Optional is meant to be used for:

The primary use of Optional is as follows:

Optional is intended to provide a limited mechanism for library method return types where there is a clear need to represent "no result," and where using null for that is overwhelmingly likely to cause errors.

A typical code smell is, instead of the code using method chaining to handle an Optional returned from some method, it creates an Optional from something that's nullable, in order to chain methods and avoid conditionals.

Also have a look at this answer by Stuart Marks "Should Optional.ofNullable() be used for null check?".

Using Optional.ofNullable() to avoid null-checks is an antipattern. You shouldn't create Optional in order to validate an object that can be null

I see nothing that can be considered to be "bad" in this snippet from your question. Yes, it's verbose, it's a price for allowing nullable fields.

if (product.getQuantityAvailable() != null && product.getQuantityReserved() != null) {
    if (product.getQuantityAvailable() < product.getQuantityReserved()) {
        System.out.println("Error");
    }
}

If you want to reduce the level of nesting, here's a null-safe alternative with Objects.compare():

if (Objects.compare(product.getQuantityAvailable(), 
                    product.getQuantityReserved(), 
                    Comparator.nullsFirst(Comparator.naturalOrder())) < 0) {
    // perform the required action
    throw new MyCustomException("a message helpful in debuging here");
}

When you have a lot of nullable fields in your objects, which you eventually expect to contain some values, *null-checks are inevitable.

Code clattered with null-checks can be difficult to read, but it doesn't imply that null-checks are the root of all evil. It's just the consequence of your design decisions.

If you want to reduce the number of null-checks in your code, then assign default values (0 for Integer, BigDecimal.ZERO, empty collections, etc.) instead of keeping null references in every place where it doesn't harm the logic of the application.

Sidenote: contrary to some languages like type TypeScript, in Java there's no notion of optional properties, which was initially mentioned in the title of the question. Practices that are suitable for one language aren't necessarily applicable in another. If you want to leverage Java to its full power, reproducing your experience with frontend languages isn't the right way to go.

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • Hi sorry for the late response. I was on holiday. I have now accepted this answer. – Rosa Aug 22 '22 at 10:23
  • Would you say that using `Optional` to do a null check when fetching data from a database is also an antipattern? For example, see the code in this question (ignore the method signature, just look at the return statement) https://stackoverflow.com/questions/51690886/why-orelseget-seems-not-to-be-called – Rosa Aug 22 '22 at 10:27
  • 1
    @Rosa Using `Optional` to perform *null-checks* is an antipattern (it doesn't matter where the data comes from) as I said in the answer. Regarding the question you refer to - have a look at the [*answer to it*](https://stackoverflow.com/a/51691096/17949945) (there's no misuse optional there). – Alexander Ivanchenko Aug 22 '22 at 11:14
  • 1
    @Rosa Also have a look at this question: [*"Should Optional.ofNullable() be used for null check?"*](https://stackoverflow.com/questions/52038417/should-optional-ofnullable-be-used-for-null-check/52048770). – Alexander Ivanchenko Aug 22 '22 at 11:15
  • 1
    @Rosa You can use methods from the [`Objects`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Objects.html#method-summary) class like `requireNonNull()` to perform validation. They were designed specifically for that purpose. – Alexander Ivanchenko Aug 22 '22 at 11:25
0

I think you should define models with default values for additional attributes instead of accept null value. Example, with an Integer attribute, default value is 0 and String attribute is empty string

Long Ngo
  • 46
  • 5
  • I thought about that but sometimes a default value doesn't make sense, so you end up using values like 0 or -1 or INFINITY which is even worse because you have to check for those explicitly. – Rosa Aug 12 '22 at 12:37
0

I would not discount Optional, OptionalInt and such, even if the persistent database column uses NULL.

Indeed it would be cumbersome:

product.getQuantityAvailable()
    .ifPresent((available) -> {
          product.getQuantityReserved()
              .ifPresent((reserved) -> {
                    if (available < reserved) {
                      System.out.println("Error");
                    }
                  });
        });

But with a map from OptionalInt to IntStream:

if (product.getQuantityAvailable()
        .map().anyMatch(av -> product.getQuantityReserved()
            .map.anyMatch(res -> av < res))) {
    System.out.println("Error");
}

You could store the predicate lambdas separately, say in the product.

The code is more readable, less errorprone, though a bit )))-ish.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • 1
    I guess having the lambdas elsewhere is a good idea, but I've just tried this and found these kind of condition checks were usually unique so I could not reuse them much. Thanks for the suggestion though I'll play with it. – Rosa Aug 12 '22 at 12:26