Null Analysis
NullPointerException thrown at runtime is one of the most common causes for failure of Java programs.
The Null Analysis tool can detect such programming errors (misuse of potential null
Java values) at compile-time.
The following example of code shows a typical Null Analysis error detection in MicroEJ SDK.
Principle
The Null Analysis tool is based on Java annotations. Each Java field, method parameter and method return value must be marked to indicate whether it can be null
or not.
Once the Java code is annotated, module projects must be configured to enable Null Analysis detection in MicroEJ SDK.
Java Code Annotation
MicroEJ defines its own annotations:
@NonNullByDefault: Indicates that all fields, method return values or parameters can never be null in the annotated package or type. This rule can be overridden on each element by using the Nullable annotation.
@Nullable: Indicates that a field, local variable, method return value or parameter can be null.
@NonNull: Indicates that a field, local variable, method return value or parameter can never be null.
MicroEJ recommends to annotate the Java code as follows:
In each Java package, create a
package-info.java
file and annotate the Java package with@NonNullByDefault
. This is a common good practice to deal with nonnull
elements by default to avoid undesired NullPointerException. It enforces the behavior which is already widely outlined in Java coding rules.In each Java type, annotate all fields, methods return values and parameters that can be null with
@Nullable
. Usually, this information is already available as textual information in the field or method Javadoc comment. The following example of code shows where annotations must be placed:
Module Project Configuration
Requirements
EDC-1.3.3 or higher is required when MicroEJ SDK 5.3.0
or higher is used. See EDC 1.3.3 Changelog for more details.
Project configuration
To enable the Null Analysis tool, a module project must be configured as follows:
In the Package Explorer, right-click on the module project and select Properties,
Navigate to Java Compiler > Errors/Warnings,
In the Null analysis section, configure options as follows:
Click on the Configure… link to configure MicroEJ annotations:
ej.annotation.Nullable
ej.annotation.NonNull
ej.annotation.NonNullByDefault
In the Annotations section, check Suppress optional errors with ‘@SuppressWarnings’ option:
This option allows to fully ignore Null Analysis errors in advanced cases using
@SuppressWarnings("null")
annotation.
If you have multiple projects to configure, you can then copy the content of the .settings
folder to an other module project.
Warning
You may lose information if your target module project already has custom parameterization or if it was created with another MicroEJ SDK version. In case of any doubt, please configure the options manually or merge with a text file comparator.
MicroEJ Libraries
Many libraries available on Central Repository are annotated with Null Analysis. If you are using a library which is not yet annotated, please contact our support team.
For the benefit of Null Analysis, some APIs have been slightly constrained compared to the Javadoc description. Here are some examples to illustrate the philosophy:
System.getProperty(String key, String def) does not accept a
null
default value, which allows to ensure the returned value is always nonnull
.Collections of the Java Collections Framework that can hold
null
elements (e.g. HashMap) do not acceptnull
elements. This allows APIs to returnnull
(e.g. HashMap.get(Object)) only when an element is not contained in the collection.
Implementations are left unchanged and still comply with the Javadoc description whether the Null Analysis is enabled or not. So if these additional constraints are not acceptable for your project, please disable Null Analysis.
Advanced Use
For more information about Null Analysis and inter-procedural analysis, please visit Eclipse JDT Null Analysis documentation.
Troubleshooting
The project cannot build anymore after Null Analysis setup
java.lang.NullPointerException
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.getMethods(BinaryTypeBinding.java:1348)
at org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding.setMethodBindings(AnnotationBinding.java:238)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createAnnotation(LookupEnvironment.java:995)
at org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding.buildTargetAnnotation(AnnotationBinding.java:191)
at org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding.addStandardAnnotations(AnnotationBinding.java:79)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.retrieveAnnotations(BinaryTypeBinding.java:1698)
at org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding.getAnnotations(ReferenceBinding.java:1054)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.evaluateTypeQualifierDefault(BinaryTypeBinding.java:2021)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.getNonNullByDefaultValue(BinaryTypeBinding.java:1999)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.scanTypeForNullDefaultAnnotation(BinaryTypeBinding.java:1943)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.cachePartsFrom(BinaryTypeBinding.java:470)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1055)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1036)
at org.eclipse.jdt.internal.compiler.Compiler.accept(Compiler.java:308)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:326)
at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.getType(PackageBinding.java:195)
at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.initDefaultNullness(PackageBinding.java:325)
at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.getDefaultNullness(PackageBinding.java:339)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.scanTypeForNullDefaultAnnotation(BinaryTypeBinding.java:1965)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.cachePartsFrom(BinaryTypeBinding.java:470)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1055)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1036)
at org.eclipse.jdt.internal.compiler.Compiler.accept(Compiler.java:308)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:326)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getType(LookupEnvironment.java:1705)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getResolvedType(LookupEnvironment.java:1633)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getResolvedJavaBaseType(LookupEnvironment.java:1645)
at org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding.buildTargetAnnotation(AnnotationBinding.java:134)
at org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding.addStandardAnnotations(AnnotationBinding.java:79)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.retrieveAnnotations(BinaryTypeBinding.java:1698)
at org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding.getAnnotations(ReferenceBinding.java:1054)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.evaluateTypeQualifierDefault(BinaryTypeBinding.java:2021)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.getNonNullByDefaultValue(BinaryTypeBinding.java:1999)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.scanTypeForNullDefaultAnnotation(BinaryTypeBinding.java:1943)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.cachePartsFrom(BinaryTypeBinding.java:470)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1055)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1036)
at org.eclipse.jdt.internal.compiler.Compiler.accept(Compiler.java:308)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:326)
at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.getType(PackageBinding.java:195)
at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.isViewedAsDeprecated(PackageBinding.java:314)
at org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding.isViewedAsDeprecated(ReferenceBinding.java:1745)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.cachePartsFrom(BinaryTypeBinding.java:566)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1055)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1036)
at org.eclipse.jdt.internal.compiler.Compiler.accept(Compiler.java:308)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:257)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getType(LookupEnvironment.java:1703)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.getNonNullByDefaultValue(BinaryTypeBinding.java:1995)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.scanTypeForNullDefaultAnnotation(BinaryTypeBinding.java:1943)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.cachePartsFrom(BinaryTypeBinding.java:470)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1055)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1036)
at org.eclipse.jdt.internal.compiler.Compiler.accept(Compiler.java:308)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:326)
at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.getType(PackageBinding.java:195)
at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.initDefaultNullness(PackageBinding.java:325)
at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.getDefaultNullness(PackageBinding.java:339)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.scanTypeForNullDefaultAnnotation(BinaryTypeBinding.java:1965)
at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.cachePartsFrom(BinaryTypeBinding.java:470)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1055)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.createBinaryTypeFrom(LookupEnvironment.java:1036)
at org.eclipse.jdt.internal.compiler.Compiler.accept(Compiler.java:308)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:326)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getType(LookupEnvironment.java:1705)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getResolvedType(LookupEnvironment.java:1633)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.getResolvedJavaBaseType(LookupEnvironment.java:1645)
at org.eclipse.jdt.internal.compiler.lookup.Scope.getJavaLangObject(Scope.java:2961)
at org.eclipse.jdt.internal.compiler.lookup.ClassScope.connectSuperclass(ClassScope.java:1065)
at org.eclipse.jdt.internal.compiler.lookup.ClassScope.connectTypeHierarchy(ClassScope.java:1246)
at org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope.connectTypeHierarchy(CompilationUnitScope.java:367)
at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.completeTypeBindings(LookupEnvironment.java:518)
at org.eclipse.jdt.internal.compiler.Compiler.internalBeginToCompile(Compiler.java:878)
at org.eclipse.jdt.internal.compiler.Compiler.beginToCompile(Compiler.java:394)
at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:444)
at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:426)
at org.eclipse.jdt.internal.core.builder.AbstractImageBuilder.compile(AbstractImageBuilder.java:386)
at org.eclipse.jdt.internal.core.builder.BatchImageBuilder.compile(BatchImageBuilder.java:214)
at org.eclipse.jdt.internal.core.builder.AbstractImageBuilder.compile(AbstractImageBuilder.java:318)
at org.eclipse.jdt.internal.core.builder.BatchImageBuilder.build(BatchImageBuilder.java:79)
at org.eclipse.jdt.internal.core.builder.JavaBuilder.buildAll(JavaBuilder.java:275)
at org.eclipse.jdt.internal.core.builder.JavaBuilder.build(JavaBuilder.java:192)
at org.eclipse.core.internal.events.BuildManager$2.run(BuildManager.java:832)
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:220)
at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:263)
at org.eclipse.core.internal.events.BuildManager$1.run(BuildManager.java:316)
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45)
at org.eclipse.core.internal.events.BuildManager.basicBuild(BuildManager.java:319)
at org.eclipse.core.internal.events.BuildManager.basicBuildLoop(BuildManager.java:371)
at org.eclipse.core.internal.events.BuildManager.build(BuildManager.java:392)
at org.eclipse.core.internal.events.AutoBuildJob.doBuild(AutoBuildJob.java:154)
at org.eclipse.core.internal.events.AutoBuildJob.run(AutoBuildJob.java:244)
at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
You may encounter the two popup windows and the full stack trace above when your version of EDC
is too old. To fix this issue, please use EDC-1.3.3 or higher with MicroEJ SDK 5.3.0
or higher.