SpotBugs

Info

SpotBugs is a successor project to deprecated FindBugs project. Migration guide. If you were using custom findbugs config before then rename it's folder to spotbugs.

Warning

In contrast to other plugins, spotbugs plugin is not bundled with gradle, but quality plugin will bring it as a dependency (v 2.0.1) and activate automatically. To use newer spotbugs plugin version simply enable plugin manually (in plugins section).

By default, plugin activates if java sources available (src/main/java).

SpotBugs configuration differ from other tools (checkstyle, pmd): instead of exact rules configuration it uses efforts level. Deeper level could reveal more bugs, but with higher mistake possibility. Default settings (max effort and medium level) are perfect for most cases. Some checks were disabled in the default filter file

Note

Special xsl file used for manual html report generation because spotbugs plugin could generate either xml or html report and not both.

Output

2 (0 / 2 / 0) SpotBugs violations were found in 2 files

[Performance | URF_UNREAD_FIELD] sample.(Sample.java:8) [priority 2 / rank 14]
    >> Unread field: sample.Sample.sample
  This field is never read. Consider removing it from the class.

...  

Counts in braces show priorities (p1/p2/p3).

Note

There is no link to spotbugs site (like other tools), because report already contains everything from there.

Tip

Both priority and rank are shown for violations: [priority 2 / rank 14]. Priority relates to spotbugsLevel setting and rank to spotbugsMaxRank.

Config

Tool config options with defaults:

quality {
    spotbugsVersion = '4.0.2'
    spotbugs = true // false to disable automatic plugin activation
    spotbugsEffort = 'max'  // min, less, more or max
    spotbugsLevel = 'medium' // low, medium, high
    spotbugsMaxRank = 20 // 1-4 scariest, 5-9 scary, 10-14 troubling, 15-20 of concern  
    spotbugsMaxHeapSize = '1g'
}

Attention

Gradle 5 reduced default memory settings and so default memory for spotbugs task become 512mb (instead of 1/4 of physical memory as it was before). To reduce the impact (as spotbugs task is memory-consuming), quality plugin sets now default memory to 1g. If your project requires more memory for spotbugs, increase it with spotbugsMaxHeapSize option: spotbugsMaxHeapSize='2g'

Note that quality pligin setting is applied only if sotbugs task was not configured manually, for example, with spotbugsMain.maxHeapSize = '2g'.

Suppress

To suppress violations you can use filter file. In this case you need to override default filter file.

Or you can use annotations. SpotBugs use custom annotations and so you need to add com.github.spotbugs:spotbugs-annotations:3.1.2 dependency (with provided scope if possible) and use:

@SuppressFBWarnings("URF_UNREAD_FIELD")

Abstract

Spotbugs can't use default @SuppressWarnings annotation because it's a source annotation and not available in bytecode.

Plugins

You may add additional spotbugs checks by declaring spotbugs plugins.

Warning

As, by default, spotbugs plugin applied automatically after configuration read, spotbugsPlugins configuration can't be used directly

You can register plugins using quality extension shortcut:

quality {
    spotbugsPlugin 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.0'
    spotbugsPlugin 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7'        
}

Note

Rules from plugins would be identified in console output:

[fb-contrib project | Correctness | FCBL_FIELD_COULD_BE_LOCAL] sample.(Sample.java:11)  [priority 2 / rank 7]
    >> Class sample.Sample defines fields that are used only as locals
  This class defines fields that are used in a locals only fashion,
  specifically private fields or protected fields in final classes that are accessed
  first in each method with a store vs. a load. This field could be replaced by one
  or more local variables.

Alternatively, you can use afterEvaluate to register directly in spotbugsPlugins configuration:

afterEvaluate {
    dependencies {
        spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7'
    }
}

Or declare spotbugs plugin manually (it will be still configured by quality plugin) and use spotbugsPlugins configuration directly:

plugins {
    id 'com.github.spotbugs' version '2.0.1'
}
dependencies {
    spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7'
}

Tip

All these approaches could work together, but better stick to one.

Available plugins

Find Security Bugs (site)

quality {
    spotbugsPlugin 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.10.0'
}

fb-contrib: A FindBugs auxiliary detector plugin (site)

qualtiy {
    spotbugsPlugin 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7'
}

Annotations

Use spotbugs-annotations to guide spotbugs nullability checks (@NonNull and @Nullable). Add com.github.spotbugs:spotbugs-annotations:3.1.2 dependency (with provided scope if possible).

Warning

Before, annotations from Jsr-305 were used (com.google.code.findbugs:jsr305), but now it is dead. Remove jsr-305 jar if it were used and use undeprecated @edu.umd.cs.findbugs.annotations.NonNull and @edu.umd.cs.findbugs.annotations.Nullable

Pay attention becuase libraries still bring-in jsr-305 jar (e.g. guava does): do not use javax.annotation.Nullable because it may lead to split package problem on java9 and above (not always)

Another alternative is chaker framework annotations: org.checkerframework:checker-qual:3.0.0. Guava already switched to use them, so if you use it you may already have these annotations.

Using checker framework annotations should be preferable because it's on the track to community acceptance as default jsr-305 replacement. Besides, it's the only advanced java types system extension and validation tool.

Hint

Even if you will use other annotations, people using checker framework with your library would still benefit from your annotations because checker framework understands almost all of them.

Summary:

  • If checker framework available (org.checkerframework:checker-qual) use it: org.checkerframework.checker.nullness.qual.Nullable
  • Otherwise, use spotbugs-annotations (com.github.spotbugs:spotbugs-annotations): edu.umd.cs.findbugs.annotations.Nullable
  • Avoid using jsr-305 directly (com.google.code.findbugs:jsr305): javax.annotation.Nullable

Example

Here is an example, which will force you to use nullability annotations.

When you use guava functions or predicates you may receive this:

[NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE] input must be nonnull but is marked as nullable 

The reason for this is that guava use @Nullable annotation, which is @Inherited, so even if you not set annotation on your own function or predicate it will still be visible.

The simplest workaround is to set @NonNull annotation on your function or predicate:

public boolean apply(@NonNull final Object input) {

Hint

NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION check was disabled because it does not allow this workaround to work

Problems resolution

Most problems appear with spotbugs configuration. Plugin by default configures only default dependencies for it, so if you modify this configuration you will have to specify all dependencies:

afterEvaluate {
    dependencies {
        spotbugs "com.github.spotbugs:spotbugs:${quality.spotbugsVersion}"
        spotbugs "org.slf4j:slf4j-simple:1.8.0-beta4"
    }
}

Important

Gradle will not show you dependencies tree for spotbugs configuration (because it doesn't show default dependencies) so to be able to see conflicts, configure it manually (as shown above). After that you can investigate with:

gradlew dependencies --configuration spotbugs
or (for exact dependency tracking)
gradlew dependencyInsight --configuration spotbugs --dependency asm

Asm

If you have problems executing spotbugs tasks like

Execution failed for task ':spotbugsMain'.
> Failed to run Gradle SpotBugs Worker
   > org/objectweb/asm/RecordComponentVisitor

(NoClassDefFoundException in stacktrace)

Then it is possible that you have incorrect asm:

gradlew dependencyInsight --configuration spotbugs --dependency org.ow2.asm:asm

org.ow2.asm:asm:7.2 (selected by rule)
...
org.ow2.asm:asm:7.3.1 -> 7.2

This may be caused by incorrect BOM usage. For example, spring dependency-management plugin configured like this:

dependencyManagement {
    imports {
        mavenBom "com.google.inject:guice-bom:4.2.3"
    }        
}

would apply to ALL configurations, including "spotbugs". In this example, guice bom will force asm 7.2 which will lead to fail.

To fix this apply BOM only to some configurations:

dependencyManagement {
    configurations(implementation, testImplementation, provided) {
        imports {
            mavenBom "com.google.inject:guice-bom:4.2.3"
        }        
    }
}

Warning

But, in this case, generated pom will lack "dependencyManagement" section (as it use only globally applied BOMs), so if resulted pom is important for you, then simply force correct asm version for spotbugs:

afterEvaluate {
    dependencies {
        spotbugs "com.github.spotbugs:spotbugs:${quality.spotbugsVersion}"
        spotbugs "org.slf4j:slf4j-simple:1.7.30"
        spotbugs "org.ow2.asm:asm:8.0.1"
    }  
}  
Spotbugs 4.0.2 depends on slf4j-simple 1.8.0-beta4, but dependency-management plugin could lower slf4-api version too and so you must choose slf4j-simple version accordingly.

Slf4j

There were some problems due to sl4j version used by spotbugs plugin mismatch with gradle's slf4j.

As in ASM case above the problem could be caused by dependency-management plugin (or something affecting all configurations).

To workaround this, plugin forces the same version of "sl4j-simple" in "spotbugs" configuration as sl4j in gradle. Like this:

afterEvaluate {
    dependencies {
        spotbugs "com.github.spotbugs:spotbugs:${quality.spotbugsVersion}"
        spotbugs "org.slf4j:slf4j-simple:1.7.30"
    }  
} 

Spotbugs 4.0.2 depends on slf4j-simple 1.8.0-beta4, but you can specify lower version if required (for compatibility).

Build dashboard plugin

If you use build-dashboard plugin, you may face an error:

Execution failed for task ':buildDashboard'.
> Could not create task ':spotbugsTest'.
   > Cannot change dependencies of dependency configuration ':spotbugs' after it has been resolved.

This is due to a bug in build-dashboard plugin, forcing initialization of all project tasks. Spotbugs create lazy tasks for all source sets and each task configures defaults for spotbugs configuration. So when build-dashboard force initialization of not used tasks, they can't apply configurations.

To workaround this simply initialize all not used spotbugs tasks manually:

afterEvaluate {
    tasks.findByName('spotbugsTest')
}

afterEvaluate required because spotbugs plugin applied after configuration and findByName forces task initialization (for lazy tasks).

New gradle plugin

Plugin still uses old spotbugs plugin 2.0.1 and new version 4.0.5 is already available. But, spotbugs plugin 4.0 was almost complete plugin rewrite, and it conceptually changes some things. Eventually, I will support this new plugin, but for now old one is working and is good enough.

New spotbugs plugin has different maven coordinates and different package, so it doesn't override old one.

If you will try to activate new spotbugs plugin manually then you will need to disable spotbubgs in quality plugin so old plugin would not be activated automatically:

quality.spotbugs = false

After disabling old plugin, configure new spotbugs plugin manually.