Code Quality Tools

Static Analysis, Linting, and Code Quality for Java

← Back to Index

Why Code Quality Matters

Code quality tools help maintain consistent, readable, and bug-free code. They catch issues early in the development cycle, reducing technical debt and making code easier to maintain.

Code Quality Benefits
  • Bug Prevention - Catch potential issues before runtime
  • Consistency - Enforce coding standards across teams
  • Maintainability - Easier to read and modify code
  • Security - Identify security vulnerabilities early
  • Knowledge Sharing - New team members learn standards quickly

Static Analysis Tools Overview

Tool Purpose Integration
SonarQube/SonarCloud Comprehensive quality platform CI/CD, IDE plugins
Checkstyle Code style enforcement Maven/Gradle, IDE
PMD Bug patterns, best practices Maven/Gradle, IDE
SpotBugs Bug detection via bytecode analysis Maven/Gradle, IDE
Error Prone Compile-time bug detection Compiler plugin

SonarQube

SonarQube is the most comprehensive code quality platform, analyzing code for bugs, vulnerabilities, code smells, and technical debt.

Key Metrics

Maven Configuration

<!-- pom.xml -->
<properties>
    <sonar.projectKey>my-project</sonar.projectKey>
    <sonar.organization>my-org</sonar.organization>
    <sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties>

<!-- Run analysis -->
# mvn sonar:sonar -Dsonar.token=YOUR_TOKEN

GitHub Actions Integration

# .github/workflows/sonar.yml
name: SonarCloud Analysis

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  sonar:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'
        cache: maven

    - name: Cache SonarCloud packages
      uses: actions/cache@v3
      with:
        path: ~/.sonar/cache
        key: ${{ runner.os }}-sonar

    - name: Build and analyze
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
      run: mvn -B verify sonar:sonar

Quality Gate

// SonarQube Quality Gate conditions (customizable)
// Default "Sonar way" gate:

New Code Coverage:        > 80%
New Duplicated Lines:     < 3%
New Maintainability Rating: A
New Reliability Rating:   A
New Security Rating:      A
New Security Hotspots:    100% reviewed

// Quality Gate status blocks CI/CD if conditions not met

Checkstyle

Checkstyle enforces coding conventions and style guidelines. It's highly configurable and can use Google or Sun coding standards.

Maven Plugin Configuration

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-checkstyle-plugin</artifactId>
    <version>3.3.1</version>
    <configuration>
        <!-- Use Google's coding style -->
        <configLocation>google_checks.xml</configLocation>
        <!-- Or use Sun's style -->
        <!-- <configLocation>sun_checks.xml</configLocation> -->
        <consoleOutput>true</consoleOutput>
        <failsOnError>true</failsOnError>
    </configuration>
    <executions>
        <execution>
            <id>validate</id>
            <phase>validate</phase>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Custom Configuration

<!-- checkstyle.xml -->
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
    "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
    "https://checkstyle.org/dtds/configuration_1_3.dtd">

<module name="Checker">
    <property name="charset" value="UTF-8"/>

    <module name="TreeWalker">
        <!-- Naming conventions -->
        <module name="ConstantName"/>
        <module name="LocalVariableName"/>
        <module name="MethodName"/>
        <module name="ParameterName"/>
        <module name="TypeName"/>

        <!-- Imports -->
        <module name="AvoidStarImport"/>
        <module name="UnusedImports"/>

        <!-- Size violations -->
        <module name="LineLength">
            <property name="max" value="120"/>
        </module>
        <module name="MethodLength">
            <property name="max" value="50"/>
        </module>

        <!-- Whitespace -->
        <module name="WhitespaceAfter"/>
        <module name="WhitespaceAround"/>

        <!-- Coding -->
        <module name="EmptyStatement"/>
        <module name="EqualsHashCode"/>
        <module name="MissingSwitchDefault"/>
    </module>
</module>

Common Checkstyle Rules

// VIOLATION: Star import
import java.util.*;  // Avoid!

// CORRECT: Explicit imports
import java.util.List;
import java.util.ArrayList;

// VIOLATION: Line too long (>120 chars)
public void processUserDataAndSendNotificationWithAllRequiredParametersAndValidation(...) { }

// VIOLATION: Missing braces
if (condition)
    doSomething();

// CORRECT: Always use braces
if (condition) {
    doSomething();
}

// VIOLATION: Constant naming
public static final int maxSize = 100;  // Should be MAX_SIZE

PMD

PMD finds common programming flaws, unused code, suboptimal code, and overly complicated expressions.

Maven Configuration

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-pmd-plugin</artifactId>
    <version>3.21.2</version>
    <configuration>
        <rulesets>
            <ruleset>/category/java/bestpractices.xml</ruleset>
            <ruleset>/category/java/codestyle.xml</ruleset>
            <ruleset>/category/java/design.xml</ruleset>
            <ruleset>/category/java/errorprone.xml</ruleset>
            <ruleset>/category/java/performance.xml</ruleset>
        </rulesets>
        <failOnViolation>true</failOnViolation>
        <printFailingErrors>true</printFailingErrors>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Common PMD Rules

// UnusedLocalVariable
public void process() {
    int unused = 5;  // PMD: Variable 'unused' is never used
}

// EmptyCatchBlock
try {
    riskyOperation();
} catch (Exception e) {
    // PMD: Empty catch block - at least log the exception!
}

// AvoidReassigningParameters
public void process(String input) {
    input = input.trim();  // PMD: Don't reassign parameters
}

// UseCollectionIsEmpty
if (list.size() == 0) { }  // PMD: Use isEmpty()
if (list.isEmpty()) { }    // Better!

// SimplifyBooleanReturns
if (condition) {
    return true;
} else {
    return false;
}
// Should be: return condition;

SpotBugs

SpotBugs (successor to FindBugs) analyzes bytecode to find potential bugs, including null pointer dereferences, infinite loops, and resource leaks.

Maven Configuration

<plugin>
    <groupId>com.github.spotbugs</groupId>
    <artifactId>spotbugs-maven-plugin</artifactId>
    <version>4.8.2.0</version>
    <configuration>
        <effort>Max</effort>
        <threshold>Low</threshold>
        <failOnError>true</failOnError>
        <plugins>
            <!-- Additional plugins -->
            <plugin>
                <groupId>com.h3xstream.findsecbugs</groupId>
                <artifactId>findsecbugs-plugin</artifactId>
                <version>1.12.0</version>
            </plugin>
        </plugins>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Common SpotBugs Findings

// NP_NULL_ON_SOME_PATH - Possible null pointer dereference
public void process(User user) {
    String name = user.getName();  // user could be null!
    name.toUpperCase();
}

// RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
String s = "hello";
if (s != null) { }  // Redundant - string literal is never null

// OBL_UNSATISFIED_OBLIGATION - Resource leak
public void readFile() {
    FileInputStream fis = new FileInputStream("file.txt");
    // SpotBugs: Stream not closed!
}

// DM_BOXED_PRIMITIVE_FOR_PARSING
int value = new Integer("123");  // Inefficient
int value = Integer.parseInt("123");  // Better

// EQ_COMPARETO_USE_OBJECT_EQUALS
// Class implements compareTo but not equals - inconsistent!

Error Prone

Error Prone is a compile-time static analysis tool from Google that catches common Java mistakes.

Maven Configuration

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.12.1</version>
    <configuration>
        <release>17</release>
        <compilerArgs>
            <arg>-XDcompilePolicy=simple</arg>
            <arg>-Xplugin:ErrorProne</arg>
        </compilerArgs>
        <annotationProcessorPaths>
            <path>
                <groupId>com.google.errorprone</groupId>
                <artifactId>error_prone_core</artifactId>
                <version>2.24.1</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

Common Error Prone Checks

// StringSplitter - split() has surprising behavior with regex
String[] parts = "a.b.c".split(".");  // Returns empty array!
String[] parts = "a.b.c".split("\\.");  // Correct

// CollectionIncompatibleType
List<String> strings = new ArrayList<>();
strings.contains(123);  // Always returns false!

// EqualsHashCode
class User {
    @Override
    public boolean equals(Object o) { ... }
    // Missing hashCode()!
}

// MissingOverride
class Child extends Parent {
    public void process() { }  // Should have @Override
}

Combining Tools

Use multiple tools together for comprehensive coverage. Each tool has different strengths.

Recommended Combination

<!-- Complete quality setup in pom.xml -->
<build>
    <plugins>
        <!-- Checkstyle - Style and formatting -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-checkstyle-plugin</artifactId>
            <version>3.3.1</version>
            <!-- ... configuration ... -->
        </plugin>

        <!-- PMD - Code patterns and best practices -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-pmd-plugin</artifactId>
            <version>3.21.2</version>
            <!-- ... configuration ... -->
        </plugin>

        <!-- SpotBugs - Bug detection -->
        <plugin>
            <groupId>com.github.spotbugs</groupId>
            <artifactId>spotbugs-maven-plugin</artifactId>
            <version>4.8.2.0</version>
            <!-- ... configuration ... -->
        </plugin>

        <!-- JaCoCo - Code coverage -->
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.11</version>
            <!-- ... configuration ... -->
        </plugin>
    </plugins>
</build>

# Run all checks
mvn verify

IDE Integration

IntelliJ IDEA

// Built-in inspections: Settings -> Editor -> Inspections

// Checkstyle plugin
// 1. Install "CheckStyle-IDEA" plugin
// 2. Settings -> Tools -> Checkstyle
// 3. Add configuration file

// SonarLint plugin
// 1. Install "SonarLint" plugin
// 2. Connect to SonarQube/SonarCloud for synchronized rules
// 3. Real-time analysis as you code

Eclipse

// Checkstyle plugin: eclipse-cs
// PMD plugin: pmd-eclipse-plugin
// SpotBugs plugin: spotbugs-eclipse-plugin
// SonarLint: Available from Eclipse Marketplace
IDE Best Practice

Configure your IDE to run static analysis on save. This provides immediate feedback and prevents issues from accumulating.

Code Formatting

Spotless (Format Enforcement)

<plugin>
    <groupId>com.diffplug.spotless</groupId>
    <artifactId>spotless-maven-plugin</artifactId>
    <version>2.41.1</version>
    <configuration>
        <java>
            <!-- Use Google Java Format -->
            <googleJavaFormat>
                <version>1.18.1</version>
                <style>GOOGLE</style>
            </googleJavaFormat>
            <!-- Or use Eclipse formatter -->
            <!-- <eclipse><file>eclipse-formatter.xml</file></eclipse> -->
            <removeUnusedImports/>
        </java>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
</plugin>

# Check formatting
mvn spotless:check

# Apply formatting
mvn spotless:apply

Best Practices

Code Quality Strategy
  • Start early - Add tools to new projects from the beginning
  • Fail the build - Don't allow new violations
  • Fix gradually - For legacy code, use baselines and fix incrementally
  • Customize rules - Disable rules that don't apply to your project
  • Review false positives - Suppress with proper justification
  • Integrate in CI/CD - Make quality gates mandatory

Suppressing False Positives

// Suppress specific SpotBugs warning
@SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH",
    justification = "Null check happens in caller")
public void process(User user) { }

// Suppress PMD warning
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public void test() { }

// Suppress Checkstyle (in checkstyle-suppressions.xml)
<suppressions>
    <suppress checks="MagicNumber" files="Constants\.java"/>
</suppressions>

// SonarQube - suppress with @SuppressWarnings or //NOSONAR
@SuppressWarnings("java:S1135")  // Track uses of "TODO" tags
public void method() {
    // TODO: implement this  //NOSONAR
}