|Most Shared

Rapid FindBugs tech coding java
20 Mar 2015at Alpharetta


  • Given a large multi-module maven project
  • and we need to run find-bugs or any static code analysis plugins
  • when I want my build to run faster,
  • then I need a mechanism to paralleize the code analysis using more threads, in addition to maven default concurrency setting


Wrote a custom Maven mojo RapidFindBugsMojo, that would start Runnable Rapider per module. Each thread running Rapider would run FindBugs across the class files in that module, and would dump results in a concurrent hashmap using a countdown latch. Finally a decision is made whether to pass or fail the build based on the bugs collected, and prints out a report. This custom rapid-maven-plugin cut 25-30% of our continuous integration cycle time.


Please refer complete code checked here. Some excerpts below..



 package com.vijayrc.maven;

 import com.vijayrc.maven.output.AllResults;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugin.logging.Log;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;

 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;

 import static edu.umd.cs.findbugs.Priorities.*;
 import static java.io.File.separator;
 import static java.util.Arrays.binarySearch;

 * Goal which runs findbugs simultaneously on sub-modules
     @Mojo(name = "findbugs")
     public class RapidFindBugsMojo extends AbstractMojo {

     @Parameter(property = "srcDir")
     private String srcDir;

     @Parameter(property = "includeFilterFile", defaultValue = "")
     private String includeFilterFile;

     @Parameter(property = "threadCount", defaultValue = "5")
     private int threadCount;

     @Parameter(property = "skip", defaultValue = "true")
     private boolean skip;

     @Parameter(property = "priority", defaultValue = "3")
     private int priority;

     @Parameter(defaultValue = "false")
     private boolean stopBuild;

     public void execute() throws MojoExecutionException, MojoFailureException {
         if (skip) return;

         Log log = getLog();
         log.info("rapid-findbugs: srcDir=" + srcDir);
         log.info("rapid-findbugs: includeFilterFile=" + includeFilterFile);
         log.info("rapid-findbugs: skip=" + skip);
         log.info("rapid-findbugs: threadCount=" + threadCount);
         log.info("rapid-findbugs: priority=" + priority);

         if (isEmpty(srcDir)) {
             log.error("rapid-findbugs: srcDir cannot be empty!");
         if (isEmpty(includeFilterFile)) {
             log.error("rapid-findbugs: includeFilterFile cannot be empty!");
         AllResults allResults = new AllResults();
         try {
             List<File> filteredModules = filter(new File(srcDir).listFiles());
             CountDownLatch latch = new CountDownLatch(filteredModules.size());
             ExecutorService executors = Executors.newFixedThreadPool(threadCount);
             try {
             for (File module : filteredModules) {
             String auxPath = module.getAbsolutePath() + separator + "target";
             String path = auxPath + separator + "classes";

             int priorityLevel = getPriorityLevel(priority);
             executors.submit(new Rapider(latch, allResults, includeFilterFile, path, log, auxPath, priorityLevel, module.getName()));
         } finally {
            log.info("rapid-findbugs: all modules completed");
         } catch (Exception e) {
            throw new MojoExecutionException(e.getMessage());
         boolean codeSafe = allResults.isCodeSafe();
         log.info("rapid-findbugs: is code safe? " + codeSafe);
         if (!codeSafe) {
            String message = allResults.print();
             if (stopBuild)
                throw new MojoFailureException(message);

     private int getPriorityLevel(int priority) {
         return binarySearch(validPriorities, priority) != 0 ? priority : LOW_PRIORITY;

     private List<File> filter(File[] modules) {
         List<File> filteredModules = new ArrayList<File>();
         for (File module : modules)
             if (module.isDirectory() && containsBinaries(module))
             return filteredModules;

     private boolean containsBinaries(File module) {
         File[] files = module.listFiles();
         if (files == null) return false;
         for (File file : files) {
            if (file.getName().equals("pom.xml") || file.getName().equals("target")) return true;
         return false;

     private boolean isEmpty(String input) {
        return input == null || srcDir.trim().length() == 0;


 package com.vijayrc.maven;

 import com.vijayrc.maven.output.AllResults;
 import com.vijayrc.maven.output.Result;
 import edu.umd.cs.findbugs.*;
 import edu.umd.cs.findbugs.config.UserPreferences;
 import org.apache.commons.io.FileUtils;
 import org.apache.maven.plugin.logging.Log;

 import java.io.File;
 import java.util.concurrent.CountDownLatch;

 import static java.io.File.separator;

 * a thread to run findbugs on a module
 public class Rapider implements Runnable {

     private CountDownLatch latch;
     private AllResults allResults;
     private int priorityLevel;
     private Log log;

     private String includeFilterFile;
     private String srcDir;
     private String moduleName;
     private String auxPath;

     public Rapider(CountDownLatch latch, AllResults allResults, String includeFilterFile, String srcDir, Log log, String auxPath, int priorityLevel, String moduleName) {
         this.log = log;
         this.latch = latch;
         this.srcDir = srcDir;
         this.allResults = allResults;
         this.includeFilterFile = includeFilterFile;
         this.auxPath = auxPath;
         this.priorityLevel = priorityLevel;
         this.moduleName = moduleName;

     public void run() {
             long id = Thread.currentThread().getId();
             try {
                 log.info("rapid-findbugs: start-module|" + moduleName + "|" + id);
                 Project project = getProject();
                 Result result = new Result();

                 FindBugs2 findBugs2 = new FindBugs2();
                 findBugs2.addFilter(includeFilterFile, true);

                 for (BugInstance bug : findBugs2.getBugReporter().getBugCollection().getCollection()) {
                 if (bug.getPriority() <= priorityLevel) {
                    result.addBug("priority="+bug.getPriority() + "|rank=" + bug.getBugRank() + "|desc=" + bug.getMessageWithPriorityType());
             allResults.add(moduleName, result);
             log.info("rapid-findbugs: end-module|" + moduleName + "|" + id);

             } catch (Exception e) {
                 log.error("rapid-findbugs: end-module|" + moduleName + "|" + e.getClass());
             } finally {

     private Project getProject() {
         Project project = new Project();
         File dir = new File(srcDir);
         for (File classFile : FileUtils.listFiles(dir, new String[]{"class", "jar"}, true)) {
         if (!classFile.getPath().contains(separator + "test" + separator))
         return project;

     private UserPreferences getUserPreferences() {
         UserPreferences preferences = UserPreferences.createDefaultUserPreferences();
         return preferences;

     private BugReporter getBugReporter(Project project) {
         BugReporter bugReporter = new BugCollectionBugReporter(project);
         return bugReporter;
comments powered by Disqus

All content except noted photos and videos copyright © Vijayaraj Chakravarthy. All rights reserved. *Any images or videos not listed as mine are copyright to their respective owners and were used under creative common license or fair use standards. If a photo or video is your material and you do not wish it to be on the site, please email me vijayrc@outlook.com and I will remove it immediately.