DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Languages
  4. When to Use JSR 305 for Nullability in Java

When to Use JSR 305 for Nullability in Java

JSR 305 became a de-facto standard despite its many problems.

Tomasz Linkowski user avatar by
Tomasz Linkowski
·
Aug. 20, 19 · Tutorial
Like (3)
Save
Tweet
Share
20.03K Views

Join the DZone community and get the full member experience.

Join For Free

JSR 305 became a de-facto standard despite its many problems. I advise to use it for nullability until something better is adopted by Kotlin and major IDEs.

Introduction

When it comes to nullability annotations in Java, there is no official standard. In this post, I describe:

  • What my requirements with respect to nullability in Java are
  • Why I decided to use JSR 305 over its alternatives (like Checker Framework) for this
  • How I decided to use it to meet my requirements.

Requirements

I love how Kotlin approaches null safety. In Kotlin, you (and the compiler!) always know if a type can store null or not:

  • String → non-null String (null-check redundant)
  • String? → nullable String (null-check required)

The exception to this rule are the infamous platform types (which occur only implicitly, when you reference unannotated Java classes):

  • String! → non-null or nullable String (null-check optional)

I'd love to bring as much of Kotlin-like null safety as possible into Java and make sure that my Java libraries can be safely consumed from Kotlin.

Therefore, my requirements are:

  1. Code clarity: ensure that when code is read, the reader knows whether a type is nullable or not.
  2. Code brevity: ensure that it's not required to explicitly mark every non-null type as being non-null.
  3. Tooling support: ensure that the tools (primarily IDEs; compiler if possible) recognize nullable types and raise warnings/errors about their incorrect usage.
  4. Kotlin interop: ensure that types from Java code are not represented as platform types in Kotlin.

JSR 305

Background

JSR 305 (Annotations for Software Defect Detection) is a Java Specification Request created in 2006, which has been dormant since 2012.

The JCP page doesn't provide many details, but we can read there that:

This JSR would attempt to develop a standard set of annotations that can assist defect detection tools. [...] Some annotations already identified as potential candidates include:

• Nullness annotations (e.g., @NonNull and @CheckForNull)

William Pugh, JSR 305

More details can be found in this presentation by the author of JSR 305, William Pugh.

Focus

In this post, I'll focus only on the following nullability annotation:

  • @Nonnull

And the following two meta-annotations:

  • @TypeQualifierDefault
  • @TypeQualifierNickname

Why? Because by combining @NonNull(when = ...) with either @TypeQualifier*, we can define custom nullability annotations that are:

  • Clear yet unobtrusive (requirements 1 & 2),
  • Widely recognized (requirements 3 & 4).

Assessment of JSR 305

Here, I tried to assemble all the pros and cons of using JSR 305 for nullability in Java.

Pros of JSR 305

  1. Honored by Kotlin for its Java interop.
  2. Honored by IntelliJ IDEA (IDEA-173544).
  3. Honored by Eclipse (518839).
  4. Embraced by some popular libraries:
    • Gradle
    • Spring
    • Reactor
    • Atlassian
    • Apache Spark
    • Apache Hadoop
    • MongoDB
    • TestNG
    • OkHttp
  5. Mark Reinhold (Chief Java Architect) seems to at least tolerate using JSR 305.

Cons of JSR 305

  1. May cause split packages in JPMS (can be patched, though).
  2. Has no true specification other than this presentation by William Pugh.
  3. Has potential licensing problems.
  4. Due to the above, libraries:
    • Never used JSR 305 and don't intend to: RxJava, AutoValue
    • Stopped using JSR 305: SLF4J, Testcontainers, Caffeine, Apache Sling
    • Use JSR 305 but want to stop using it: Guava, rsocket
  5. In the future, Kotlin might support a non-JSR-305 mechanism for its Java interop (KT-21408).

Response to Criticism

Some experts (like Lukas Eder) advise not to worry about annotating your code will nullability annotations:

You can spare yourself the work of adding a @NonNull annotation on 99 percent of all of your types just to shut up your IDE, in case you turned on those warnings.

Lukas Eder

Fortunately, JSR 305 allows you to mark 99 percent of all your types without annotating all of them explicitly, thanks to its @TypeQualifierDefault meta-annotation, which can be used on packages! True, this requires creating a package-info.java for every package and putting the annotation there, but it's still easier than annotating every type.

In the future, I'd like to write a simple Gradle plugin that'd verify if the annotation is indeed present on every package.

Basic Java Annotations

I have created a simple library (named Basic Java Annotations) that takes the following approach to nullability:

Everything is non-null by default, unless explicitly annotated as nullable.

For this purpose, the library provides two nullability annotations:

  1. @NonNullPackage:
    • Annotated with: @TypeQualifierDefault
    • Targets: packages
    • Affects: all type uses within an annotated package
    • Similar to: @NonNullApi + @NonNullFields in Spring
    • Example usage
  2. @NullOr:
    • Annotated with: @TypeQualifierNickname
    • Targets: type uses (e.g. in fields, methods, parameters, local variables, etc.)
    • Affects: the annotated type use
    • Similar to: @Nullable in Spring

So after you annotate your package like below:

@NonNullPackage
package pl.tlinkowski.sample.api.annotated.nullability;


You get the following mappings between Kotlin and Java:

  • String (Kotlin) / String (Java) → non-null String
  • String? (Kotlin) / @NullOr String (Java) → nullable String

This answers how I decided to use JSR 305.

Annotation Naming

Two words about naming — I chose NullOr over Nullable:

  • to avoid potential import conflicts with all the other @Nullable annotations
  • because I like how @NullOr String reads as "null or String"

Dependency Scope

Note that thanks to the possibility of creating custom JSR-305-based annotations, I was able to define JSR 305 as a non-transitive dependency (= implementation configuration).

In other words, the users of Basic Java Annotations won't be able to reference JSR 305 types (which is good, IMO).

Alternatives

Much has been written on the Internet about which Java annotations to use for nullability. The best summaries I have found so far are this StackOverflow answer and this Checker Framework resource.

I won't be going into details here, and I'll cover only the most interesting alternative (and the only one that — to the best of my knowledge — also lets you annotate packages).

Checker Framework

Checker Framework is a beast! It has almost 15k commits on GitHub, over 100 releases, several ways of integrating with external tools, and a 34-chapter-long manual. It is a very complex framework, providing annotations and checkers regarding:

  • Nullability (AKA "nullness"; I still struggle to see the difference between these terms),
  • Interning,
  • Locking,
  • And many more.

With respect to nullability, Checker Framework provides @Nullable and @NonNull annotations, which are recognized by major IDEs and Kotlin.

The framework also provides a complex defaults mechanism, from which the most suitable for us is @DefaultQualifier, which can be used on packages.

Replacing JSR 305 with Checker Framework in Basic Annotations would look like this, which can be summarized as:

  1. implementation(...) → api(...) (no custom annotations, so we need to expose the dependency)
  2. @NonNullPackage → @DefaultQualifier(NonNull.class)
  3. @NullOr → @Nullable

How does it relate to our goals?

  1. Code clarity: OK.
  2. Code brevity: OK.
  3. Tooling support: IntelliJ recognizes the explicit @Nullable annotations but... doesn't recognize @DefaultQualifier. So, FAIL!
  4. Kotlin interop: Kotlin compiler also recognizes the explicit @Nullable annotations but doesn't recognize @DefaultQualifier (no errors, no warnings). Again, FAIL!

As you can see, this answers why I chose JSR 305 over Checker Framework.


With respect to point 3 (Tooling support), I didn't mention the Java compiler. And Checker Framework provides a Gradle plugin for this! However, that's what happened when I applied it:

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':sample-java-usage:compileJava'.
> org.checkerframework.javacutil.UserError: The Checker Framework must be run under JDK 1.8.  You are using version 12,000000.

Check mate!

It turns out the manual actually mentions this JDK 8 requirement (which seems a strange requirement to me), but — because this manual is so huge — I missed it easily.

So, no Checker Framework + JPMS yet.

Summary

In this post, I showed how JSR 305 is (currently) the only library that can meet the following four nullability-related requirements:

  1. Code clarity
  2. Code brevity
  3. Tooling support
  4. Kotlin interop

I also showed how I met those requirements by using JSR 305 as an implementation dependency in my Basic Annotations library.

Thanks for reading!

Java (programming language) Annotation Kotlin (programming language) intellij Framework Requirement

Published at DZone with permission of Tomasz Linkowski. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Spring Boot vs Eclipse MicroProfile: Resident Set Size (RSS) and Time to First Request (TFR) Comparative
  • Testing Repository Adapters With Hexagonal Architecture
  • Browser Engines: The Crux of Cross-Browser Compatibility
  • Best Practices for Writing Clean and Maintainable Code

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: