Skip to content

First run

David Bürgin edited this page Apr 3, 2016 · 18 revisions

First run

Maven likes to talk, so expect a fair bit of output produced by mvn -Pchecker compile. Here comes the first batch of diagnostics.

Annotating possible null references

Scroll upwards over the verbose output for a bit, and there is this:

...
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java:
[30,8] [initialization.fields.uninitialized] the constructor does not initialize fields: name
[ERROR] src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java:
[30,8] [initialization.fields.uninitialized] the constructor does not initialize fields: id
[ERROR] src/main/java/org/springframework/samples/petclinic/model/Pet.java:
[49,8] [initialization.fields.uninitialized] the constructor does not initialize fields: birthDate, type, owner, visits
[ERROR] src/main/java/org/springframework/samples/petclinic/model/Owner.java:
[46,8] [initialization.fields.uninitialized] the constructor does not initialize fields: address, city, telephone, pets
[ERROR] src/main/java/org/springframework/samples/petclinic/model/Owner.java:
[137,16] [return.type.incompatible] incompatible types in return.
  found   : null
  required: @Initialized @NonNull Pet
...
...
[INFO] 18 errors
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

The checker found 18 errors.

The ones at the top seem quite similar. ‘Named entity’, ‘pet’, ‘owner’ … these are the entities from the domain model of the Pet Clinic.

Open the class in the first error message, NamedEntity. The message says that the constructor doesn’t initialise the field name. True, but what’s wrong with that?

@MappedSuperclass
public class NamedEntity extends BaseEntity {

    @Column(name = "name")
    private String name;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Recall that the Checker Framework assumes that unannotated reference types are non-null by default. So, the ‘name’ field is really of type @NonNull String – but a field can only be @NonNull if it is initialised to some non-null value, and that doesn’t happen here. A new NamedEntity() will always have its name set to null. There’s no denying that name is @Nullable, and that is what we must declare:

    @Column(name = "name")
    @Nullable
    private String name;

Mutable fields like this one usually come with getters and setters. If the field may be null, the getter may return null, and the setter may accept null (run the checker again to see if this reasoning is right). Thus:

    @Nullable public String getName() {
        return name;
    }

    public void setName(@Nullable String name) {
        this.name = name;
    }

Notice in these examples how I’m putting the annotations on the field and on the method. This is equivalent to putting the annotation on the type directly, as in the following snippet. Since I chose to go with the JSR 305 annotations though, type use annotation is not an option.

    private @Nullable String name;
    public @Nullable String getName() { /* ... */ }

Annotating type use is a new capability of the Java language that arrived with release 1.8, and is definitely the right way of using nullability annotations from here on out.

By the way, for very short one-line methods like these I like putting the method annotations inline, but that’s just a matter of taste.

Iterate

Even for a small project like this one running the checker takes time. When there are many similar errors, addressing them in a batch speeds things up.

Try annotating all the entities in org.springframework.samples.petclinic.model in the same way. This amounts to annotating

  • fields
  • getters
  • setters

as @Nullable.

I have committed my changes in commit 33aaa27. This commit is quite big. The annotation work we had to do on the entities was considerable: about 45 @Nullable annotations. The good news is that this was already the bulk of @Nullable annotations in this tutorial. Still, let’s take a moment to understand what required so much work.

The annotations we introduced all had to be placed on the attributes of database entities. The entities of the Pet Clinic follow the conventions of the Java Persistence API (JPA). JPA is a venerable old spec heavily dependent on mutable objects whose fields are null by default.

Pet pet = new Pet();
pet.setName("Fip");
pet.setOwner(owner);

From a modern programming perspective this is unfortunate. Nowadays we favour immutable value classes and non-null defaults. A modern design for a Pet entity might include a builder or static factory to ensure that fields required to be non-null by the business domain are enforced to be non-null on the language level.

Pet pet = Pet.builder()
        .withName("Fip")
        .withOwner(owner)
        .build();

The lesson here is that whenever a programming model runs squarely in the face of the null-safe programming practices we talked about earlier, introducing nullness annotations can be an awful lot of work.

Let’s move on. Run the checker again on the improved code – mvn -Pchecker compile –, we’re about to uncover a first bug.

Clone this wiki locally