Linting and Formatting
Why Code Quality Tools Matter
Code quality tools automate detection of bugs, code smells, style violations, and security vulnerabilities. Manual code reviews alone cannot consistently catch all issues.
Core Benefits:
- Early bug detection: Find bugs before they reach production
- Consistency: Enforce coding standards across team
- Security: Detect common vulnerabilities automatically
- Maintainability: Prevent code smell accumulation
- Learning: Teams learn from automated feedback
Problem: Manual code reviews are time-consuming, inconsistent, and miss subtle bugs that automated tools catch immediately.
Solution: Use automated linting, formatting, and static analysis tools integrated into build process and IDE.
Code Quality Tool Comparison
| Tool | Type | Pros | Cons | Use When |
|---|---|---|---|---|
| Checkstyle | Style checker | Fast, configurable, widely adopted | Style only (no bugs) | Enforce coding standards |
| SpotBugs | Bug detector | Finds real bugs, low false positives | Limited to common patterns | Detect common bugs |
| PMD | Code smell detector | Detects design issues, extensible | More false positives | Find code smells |
| Error Prone | Compile-time checker | Fast (compile-time), Google-backed | Limited to javac integration | Catch bugs during compilation |
| SonarQube | Quality platform | Comprehensive, metrics, history | Resource-intensive | Enterprise quality governance |
| Google Java Format | Formatter | Deterministic, no config needed | No customization | Consistent formatting (Google style) |
| Manual reviews | Human inspection | Context-aware, architectural insights | Time-consuming, inconsistent | Architecture and design decisions |
Recommendation: Use Checkstyle (style), SpotBugs (bugs), PMD (code smells), and Google Java Format (formatting) in combination. Add SonarQube for centralized quality metrics.
Recommended progression: Start with manual reviews to understand quality issues → Add automated tools to catch common problems → Integrate comprehensive quality platform.
Manual Code Reviews
Human code reviews provide context-aware feedback that automated tools cannot replicate. Use checklists to ensure consistency.
Code Review Checklist
Correctness:
- Logic implements requirements correctly
- Edge cases handled (null, empty, boundary values)
- Error handling appropriate for failure scenarios
- No off-by-one errors in loops or array access
Design:
- Single Responsibility Principle followed
- Dependencies injected rather than hard-coded
- Appropriate abstraction level
- No premature optimization
Testing:
- Unit tests cover core logic
- Test names clearly describe scenarios
- Test data realistic and varied
- No test interdependencies
Security:
- Input validation at system boundaries
- No SQL injection or XSS vulnerabilities
- Sensitive data not logged or exposed
- Authentication and authorization appropriate
Performance:
- No obvious performance bottlenecks
- Database queries efficient (no N+1 queries)
- Resources properly closed (files, connections)
- Appropriate data structures chosen
Maintainability:
- Code self-documenting with clear names
- Complex logic explained with comments
- No code duplication
- Consistent with codebase style
Why Manual Reviews Are Insufficient
Limitations:
- Time-consuming: Thorough reviews take significant time
- Inconsistent: Different reviewers have different standards
- Fatigue: Humans miss issues in large changesets
- Bias: Reviewers may overlook familiar patterns
- Scale: Cannot review every line in large codebases
- Knowledge gaps: Reviewers may not know all anti-patterns
Before: Manual reviews only, inconsistent quality After: Automated tools catch common issues, humans focus on architecture and design
Checkstyle
Checkstyle enforces coding standards and style conventions. It checks formatting, naming, imports, and other stylistic aspects.
Adding Checkstyle
Maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>Gradle:
plugins {
id 'checkstyle'
}
checkstyle {
toolVersion = '10.12.7'
configFile = file("${rootDir}/checkstyle.xml")
}
// Fail build on violations
tasks.withType(Checkstyle) {
reports {
xml.required = true
html.required = true
}
}Checkstyle Configuration
Create checkstyle.xml with rules to enforce.
Basic 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"/>
<property name="severity" value="error"/>
<property name="fileExtensions" value="java"/>
<!-- TreeWalker checks individual Java source files -->
<module name="TreeWalker">
<!-- Naming Conventions -->
<module name="TypeName">
<property name="format" value="^[A-Z][a-zA-Z0-9]*$"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
</module>
<module name="ConstantName">
<property name="format" value="^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/>
</module>
<module name="LocalVariableName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
</module>
<!-- Imports -->
<module name="AvoidStarImport"/>
<module name="UnusedImports"/>
<module name="RedundantImport"/>
<module name="ImportOrder">
<property name="groups" value="java,javax,org,com"/>
<property name="ordered" value="true"/>
<property name="separated" value="true"/>
</module>
<!-- Whitespace -->
<module name="WhitespaceAround"/>
<module name="WhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<!-- Blocks -->
<module name="LeftCurly"/>
<module name="RightCurly"/>
<module name="NeedBraces"/>
<module name="EmptyBlock"/>
<!-- Coding -->
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<module name="StringLiteralEquality"/>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<!-- Design -->
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="VisibilityModifier"/>
<!-- Miscellaneous -->
<module name="ArrayTypeStyle"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
</module>
<!-- File checks -->
<module name="FileTabCharacter"/>
<module name="NewlineAtEndOfFile"/>
</module>Common Checkstyle Rules
Naming conventions:
// GOOD: Class name PascalCase
public class PaymentService { }
// => PascalCase: first letter uppercase, each word capitalized
// => Naming convention: matches Java language standards for classes
// BAD: Class name lowercase
public class paymentService { }
// => VIOLATION: class names must start with uppercase letter
// => Checkstyle error: TypeName check fails
// GOOD: Method name camelCase
public void processPayment() { }
// => camelCase: first letter lowercase, subsequent words capitalized
// => Convention: standard Java naming for methods and variables
// BAD: Method name PascalCase
public void ProcessPayment() { }
// => VIOLATION: method names must start with lowercase letter
// => Checkstyle error: MethodName check fails
// => Looks like class: confuses readers
// GOOD: Constant UPPER_SNAKE_CASE
private static final int MAX_RETRIES = 3;
// => UPPER_SNAKE_CASE: all caps, words separated by underscores
// => static final: constants use screaming snake case convention
// BAD: Constant camelCase
private static final int maxRetries = 3;
// => VIOLATION: constants must use UPPER_SNAKE_CASE
// => Checkstyle error: ConstantName check failsImport rules:
// BAD: Star imports
import java.util.*;
// => VIOLATION: star import (*) imports entire package
// => Problems: pollutes namespace, hides which classes used
// => Checkstyle error: AvoidStarImport check fails
import com.example.*;
// => Wildcard import: imports all public classes from package
// => Conflicts: higher risk of class name collisions
// GOOD: Explicit imports
import java.util.List;
// => Explicit import: only imports List interface
// => Clear dependency: shows exactly which classes used
import java.util.ArrayList;
// => Specific import: imports only ArrayList implementation
import com.example.PaymentService;
// => Fully qualified: shows exact class dependency
// => Readability: easier to understand code dependencies
// BAD: Unused import
import java.util.Map; // Not used in code
// => VIOLATION: imports class never used in file
// => Checkstyle error: UnusedImports check fails
// => Dead code: adds noise, confuses readers
// GOOD: Only used imports
import java.util.List;
// => Used import: List actually referenced in code
// => Clean: only necessary imports presentWhitespace rules:
// GOOD: Proper spacing
public class Example {
// => Space before brace: "Example {" not "Example{"
// => WhitespaceAround: spaces around operators and keywords
public void method(int x, int y) {
// => Space after comma: "x, int y" not "x,int y"
// => WhitespaceAfter: comma, semicolon spacing
if (x > 0) {
// => Space after keyword: "if (" not "if("
// => Space around operator: "x > 0" not "x>0"
// => Readability: spacing improves visual parsing
return;
}
}
}
// BAD: Missing spaces
public class Example{
// => VIOLATION: no space before brace "Example{" should be "Example {"
// => Checkstyle error: WhitespaceAround check fails
public void method(int x,int y){
// => VIOLATION: missing space after comma "x,int" should be "x, int"
// => VIOLATION: no space before brace "{" after parameter list
if(x>0){
// => VIOLATION: no space after if keyword "if(" should be "if ("
// => VIOLATION: no spaces around operator "x>0" should be "x > 0"
// => Hard to read: cramped spacing reduces readability
return;
}
}
}Running Checkstyle
# Maven
mvn checkstyle:check
# Generate HTML report
mvn checkstyle:checkstyle
# Report: target/site/checkstyle.html
# Gradle
./gradlew checkstyleMain checkstyleTest
# Reports: build/reports/checkstyle/Suppressing Violations
Suppress specific violations when necessary (use sparingly).
Suppress in code:
// Suppress specific check
@SuppressWarnings("checkstyle:MagicNumber")
// => @SuppressWarnings: annotation disables specific Checkstyle check
// => checkstyle:MagicNumber: suppresses magic number warnings for this method
public void calculate() {
int result = value * 100; // Magic number acceptable here
// => 100 allowed: magic number suppression permits literal
// => Use sparingly: document why suppression needed
}
// Suppress for entire file
// checkstyle:off
// => Comment directive: disables all Checkstyle checks below this line
public class LegacyCode {
// => Legacy code: may have many violations during migration
// Legacy code with many violations
// => Temporary suppression: should fix violations and remove later
}
// checkstyle:on
// => Re-enables checks: subsequent code must follow standardsSuppress in configuration:
<module name="SuppressionFilter">
<property name="file" value="checkstyle-suppressions.xml"/>
</module>checkstyle-suppressions.xml:
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
<!-- Suppress all checks for generated code -->
<suppress files=".*[/\\]generated[/\\].*" checks=".*"/>
<!-- Suppress specific check for specific files -->
<suppress files="LegacyService\.java" checks="MethodLength"/>
</suppressions>SpotBugs
SpotBugs (successor to FindBugs) detects common bugs through bytecode analysis. It finds real bugs like null pointer dereferences, resource leaks, and threading issues.
Adding SpotBugs
Maven:
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.3.0</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<failOnError>true</failOnError>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>Gradle:
plugins {
id 'com.github.spotbugs' version '6.0.6'
}
spotbugs {
effort = 'max'
reportLevel = 'low'
}
spotbugsMain {
reports {
html.required = true
xml.required = false
}
}Bug Categories
SpotBugs detects bugs in multiple categories.
Common bug patterns:
| Category | Example Bugs | Severity |
|---|---|---|
| Correctness | Null pointer dereference, equals() comparison bugs | High |
| Bad practice | Ignored return value, equals() without hashCode() | Medium |
| Dodgy code | Dead local store, useless control flow | Low-Med |
| Performance | Inefficient string concatenation in loops | Medium |
| Multithreaded | Inconsistent synchronization, double-checked locking | High |
| Security | SQL injection, XSS vulnerabilities | High |
| Malicious code | Public mutable fields, finalizer vulnerabilities | Medium |
Example bugs detected:
// BUG: Null pointer dereference
public void process(String value) {
// => BUG: calls method on potentially null value
if (value == null) {
// => Null check: detects null case
System.out.println("Null value");
// => Logs null: but doesn't return, continues execution
}
// SpotBugs: Possible null pointer dereference
// => SpotBugs warning: value could be null at this point
System.out.println(value.length());
// => CRASH RISK: NullPointerException if value is null
// => Bug detected: if condition doesn't prevent continuation
}
// FIXED: Proper null handling
public void process(String value) {
// => FIXED: early return prevents null pointer dereference
if (value == null) {
// => Guard clause: checks null condition
System.out.println("Null value");
// => Logs null case
return;
// => Early exit: prevents execution of value.length() when null
}
System.out.println(value.length());
// => Safe call: guaranteed non-null after guard clause
}
// BUG: equals() without hashCode()
public class User {
// => BUG: violates equals-hashCode contract
private String id;
@Override
public boolean equals(Object obj) {
// => Overrides equals: custom equality logic
if (!(obj instanceof User)) return false;
// => Type check: ensures obj is User instance
return this.id.equals(((User) obj).id);
// => ID comparison: considers users equal if IDs match
}
// SpotBugs: equals() defined without hashCode()
// => VIOLATION: equals() overridden but hashCode() not implemented
// => Contract broken: equal objects may have different hash codes
// => HashMap/HashSet broken: cannot find equal objects
}
// FIXED: Implement both
public class User {
// => FIXED: implements both equals() and hashCode()
private String id;
@Override
public boolean equals(Object obj) {
// => Equality: custom comparison logic
if (!(obj instanceof User)) return false;
return this.id.equals(((User) obj).id);
// => ID-based equality: users equal if IDs equal
}
@Override
public int hashCode() {
// => hashCode implementation: satisfies equals-hashCode contract
return Objects.hash(id);
// => Objects.hash(): generates hash from fields used in equals()
// => Contract satisfied: equal objects have same hash code
// => HashMap/HashSet work: can find equal objects in hash-based collections
}
}Running SpotBugs
# Maven
mvn spotbugs:check
# Generate HTML report
mvn spotbugs:spotbugs
# Report: target/spotbugsXml.xml, target/site/spotbugs.html
# Gradle
./gradlew spotbugsMain spotbugsTest
# Reports: build/reports/spotbugs/Excluding False Positives
Create exclusion filter for false positives.
spotbugs-exclude.xml:
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<!-- Exclude specific bug in specific class -->
<Match>
<Class name="com.example.LegacyService"/>
<Bug pattern="NP_NULL_ON_SOME_PATH"/>
</Match>
<!-- Exclude entire class from analysis -->
<Match>
<Class name="com.example.GeneratedCode"/>
</Match>
<!-- Exclude specific bug pattern everywhere -->
<Match>
<Bug pattern="URF_UNREAD_FIELD"/>
</Match>
<!-- Exclude generated code -->
<Match>
<Package name="~.*\.generated\..*"/>
</Match>
</FindBugsFilter>Maven configuration:
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.3.0</version>
<configuration>
<excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile>
</configuration>
</plugin>PMD
PMD detects code smells, potential bugs, and inefficient code through source code analysis. It’s more opinionated than SpotBugs.
Adding PMD
Maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.21.2</version>
<configuration>
<rulesets>
<ruleset>/rulesets/java/quickstart.xml</ruleset>
</rulesets>
<failOnViolation>true</failOnViolation>
<printFailingErrors>true</printFailingErrors>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>Gradle:
plugins {
id 'pmd'
}
pmd {
toolVersion = '6.55.0'
ruleSetFiles = files("pmd-ruleset.xml")
ignoreFailures = false
}PMD Rule Categories
Common rule sets:
| Rule Set | Focus | Example Rules |
|---|---|---|
| Best Practices | General coding best practices | UnusedPrivateMethod, SystemPrintln |
| Code Style | Naming and formatting conventions | ShortVariable, LongVariable |
| Design | Design flaws and complexity | GodClass, TooManyMethods |
| Documentation | Comment and documentation requirements | UncommentedEmptyConstructor |
| Error Prone | Code likely to cause errors | EmptyCatchBlock, CloseResource |
| Multithreading | Threading issues | AvoidThreadGroup, AvoidSynchronizedAtMethodLevel |
| Performance | Performance anti-patterns | AvoidInstantiatingObjectsInLoops |
| Security | Security vulnerabilities | HardCodedCryptoKey |
Custom PMD Ruleset
Create custom ruleset (pmd-ruleset.xml).
pmd-ruleset.xml:
<?xml version="1.0"?>
<ruleset name="Custom Rules"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0
https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>Custom PMD ruleset for project</description>
<!-- Best Practices -->
<rule ref="category/java/bestpractices.xml">
<!-- Exclude specific rules -->
<exclude name="JUnitTestsShouldIncludeAssert"/>
</rule>
<!-- Code Style -->
<rule ref="category/java/codestyle.xml/ShortVariable">
<properties>
<property name="minimum" value="3"/>
</properties>
</rule>
<rule ref="category/java/codestyle.xml/LongVariable">
<properties>
<property name="maximum" value="40"/>
</properties>
</rule>
<!-- Design -->
<rule ref="category/java/design.xml">
<exclude name="LawOfDemeter"/>
</rule>
<!-- Error Prone -->
<rule ref="category/java/errorprone.xml"/>
<!-- Performance -->
<rule ref="category/java/performance.xml"/>
<!-- Security -->
<rule ref="category/java/security.xml"/>
</ruleset>Running PMD
# Maven
mvn pmd:check
# Generate HTML report
mvn pmd:pmd
# Report: target/site/pmd.html
# Gradle
./gradlew pmdMain pmdTest
# Reports: build/reports/pmd/Common PMD Violations
Example violations:
// VIOLATION: EmptyCatchBlock
try {
// => Try block: executes risky operation
riskyOperation();
// => May throw exception: but catch block ignores it
} catch (Exception e) {
// => Empty catch: swallows exception without handling
// Empty catch block
// => PMD VIOLATION: EmptyCatchBlock rule triggered
// => Silent failure: errors go unnoticed, debugging impossible
}
// FIXED: Handle or log exception
try {
// => Try block: same risky operation
riskyOperation();
} catch (Exception e) {
// => Catches exception: proper error handling
logger.error("Risk operation failed", e);
// => Logs error: records failure with stack trace for debugging
throw new ServiceException("Operation failed", e);
// => Wraps exception: converts to domain exception, propagates to caller
// => Proper handling: errors visible, actionable, traceable
}
// VIOLATION: AvoidInstantiatingObjectsInLoops
for (int i = 0; i < 1000; i++) {
// => Loop: executes 1000 times
Object temp = new Object(); // Creates 1000 objects
// => PMD VIOLATION: creates new object every iteration
// => Performance impact: 1000 objects allocated, garbage collected
// => Memory churn: unnecessary allocations strain GC
process(temp);
}
// FIXED: Reuse object if possible
Object temp = new Object();
// => Creates once: single object allocation before loop
for (int i = 0; i < 1000; i++) {
// => Reuses object: same instance for all iterations
process(temp);
// => Better performance: one allocation instead of 1000
// => Reduced GC pressure: fewer objects to collect
}
// VIOLATION: UnusedPrivateMethod
public class Service {
// => Service class with dead code
private void helperMethod() {
// => PMD VIOLATION: UnusedPrivateMethod detected
// Never called
// => Dead code: method defined but never invoked
// => Code smell: confuses readers, increases maintenance burden
}
}
// FIXED: Remove unused method
public class Service {
// => Clean class: no dead code
// Method removed
// => Improved: only necessary methods remain
}Error Prone
Error Prone is a compile-time static analysis tool from Google that catches common Java mistakes during compilation.
Adding Error Prone
Maven (with Compiler Plugin):
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>21</source>
<target>21</target>
<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>
</plugins>
</build>Gradle:
plugins {
id 'java'
id 'net.ltgt.errorprone' version '3.1.0'
}
dependencies {
errorprone 'com.google.errorprone:error_prone_core:2.24.1'
}
tasks.withType(JavaCompile) {
options.errorprone.enabled = true
}Error Prone Bug Patterns
Error Prone detects bugs at compile time.
Common patterns:
// BUG: Precedence confusion
if (x = y) { // Assignment instead of comparison
// => ERROR PRONE BUG: assignment (=) instead of comparison (==)
// => Assigns y to x: always evaluates to y's value (truthy if non-zero)
// Error Prone: "Assignment in condition"
// => Compile-time error: Error Prone catches this during compilation
// => Common typo: single = instead of double ==
}
// FIXED
if (x == y) {
// => FIXED: proper comparison operator (==)
// Proper comparison
// => Compares values: returns boolean (true if equal, false if not)
// => Error Prone passes: correct comparison operator
}
// BUG: String comparison
if (str == "test") { // Reference comparison
// => ERROR PRONE BUG: reference equality (==) instead of value equality
// => Compares references: only true if same object in memory
// Error Prone: "StringEquality"
// => Wrong for strings: "test" and new String("test") are different references
// => Compile-time warning: Error Prone detects string == pattern
}
// FIXED
if (str.equals("test")) {
// => FIXED: value comparison using equals()
// Value comparison
// => Compares content: returns true if strings have same characters
// => Correct for strings: works regardless of object identity
}
// BUG: Collection incompatible type
List<String> list = Arrays.asList("a", "b");
list.contains(123); // Wrong type
// Error Prone: "CollectionIncompatibleType"
// FIXED
list.contains("123"); // Correct typeConfiguration
Disable specific checks:
tasks.withType(JavaCompile) {
options.errorprone {
// Disable specific check
disable("StringSplitter")
// Set severity
error("NullAway")
warn("FallThrough")
}
}Code Formatting
Consistent code formatting improves readability and reduces diff noise in version control.
Google Java Format
Google Java Format is a deterministic formatter with zero configuration.
Maven:
<plugin>
<groupId>com.spotify.fmt</groupId>
<artifactId>fmt-maven-plugin</artifactId>
<version>2.21.1</version>
<executions>
<execution>
<goals>
<goal>format</goal>
</goals>
</execution>
</executions>
</plugin># Format code
mvn fmt:format
# Check formatting
mvn fmt:checkGradle:
plugins {
id 'com.diffplug.spotless' version '6.23.3'
}
spotless {
java {
googleJavaFormat('1.18.1')
}
}# Format code
./gradlew spotlessApply
# Check formatting
./gradlew spotlessCheckBenefits:
- Zero config: No configuration debates
- Deterministic: Always produces same output
- Fast: Quick formatting
- Google style: Follows Google Java Style Guide
EditorConfig
EditorConfig maintains consistent coding styles across different editors.
.editorconfig:
# Root configuration
root = true
# Java files
[*.java]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
max_line_length = 100
# XML files
[*.xml]
indent_style = space
indent_size = 2
# Properties files
[*.properties]
charset = utf-8SonarQube
SonarQube is an enterprise code quality platform providing centralized metrics, issue tracking, and quality gates.
SonarQube Architecture
Components:
- SonarQube Server: Web interface, database, analysis engine
- Scanner: Analyzes code and sends results to server
- Quality Gate: Pass/fail criteria for code quality
- Quality Profile: Rule configuration for analysis
Adding SonarQube Scanner
Maven:
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.10.0.2594</version>
</plugin># Run analysis (requires SonarQube server)
mvn clean verify sonar:sonar \
-Dsonar.projectKey=myproject \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.login=sqp_tokenGradle:
plugins {
id 'org.sonarqube' version '4.4.1.3373'
}
sonar {
properties {
property "sonar.projectKey", "myproject"
property "sonar.host.url", "http://localhost:9000"
}
}./gradlew sonar \
-Dsonar.login=sqp_tokenQuality Metrics
SonarQube tracks multiple quality metrics.
Key metrics:
| Metric | Description | Target |
|---|---|---|
| Coverage | Percentage of code covered by tests | ≥ 80% |
| Duplications | Percentage of duplicated code blocks | < 3% |
| Bugs | Reliability issues | 0 (or minimal) |
| Vulnerabilities | Security issues | 0 |
| Code Smells | Maintainability issues | < 0.5% density |
| Technical Debt | Estimated time to fix all issues | < 5% ratio |
| Cognitive Complexity | How hard code is to understand | Low |
Quality Gates
Quality gates define pass/fail criteria for builds.
Example quality gate:
Conditions:
- Coverage ≥ 80%
- Duplicated Lines < 3%
- New Bugs = 0
- New Vulnerabilities = 0
- New Code Smells ≤ 5
- Security Rating = A
- Maintainability Rating ≤ AFail build on quality gate:
# Maven
mvn verify sonar:sonar -Dsonar.qualitygate.wait=true
# Gradle
./gradlew sonar -Dsonar.qualitygate.wait=trueCI Integration
GitHub Actions example:
name: SonarQube Analysis
on: [push, pull_request]
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones disabled for better analysis
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: "21"
distribution: "temurin"
- name: Cache SonarQube packages
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
- name: Build and analyze
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
run: mvn verify sonar:sonar -Dsonar.qualitygate.wait=trueBest Practices
Failing Builds on Violations
Configure tools to fail builds when violations occur.
Checkstyle:
<configuration>
<failOnViolation>true</failOnViolation>
</configuration>SpotBugs:
<configuration>
<failOnError>true</failOnError>
</configuration>PMD:
<configuration>
<failOnViolation>true</failOnViolation>
</configuration>Gradual Adoption
Don’t enable all rules at once on existing codebases.
Adoption strategy:
- Start with errors only: Enable critical bug detection
- Add style checks: Enforce on new code only
- Refactor gradually: Fix violations in modules being modified
- Increase strictness: Tighten rules as codebase improves
- Full enforcement: Apply all rules to entire codebase
Example (gradual Checkstyle):
<!-- Phase 1: Critical only -->
<module name="NullPointerException"/>
<module name="EmptyCatchBlock"/>
<!-- Phase 2: Add naming -->
<module name="TypeName"/>
<module name="MethodName"/>
<!-- Phase 3: Add formatting -->
<module name="WhitespaceAround"/>
<module name="Indentation"/>Team Consensus on Rules
Process:
- Review defaults: Examine default rule sets
- Discuss preferences: Team meeting to discuss controversial rules
- Document decisions: Record rationale for rule choices
- Trial period: Test rules for 2-4 weeks
- Adjust: Fine-tune based on feedback
- Lock: Commit final configuration to version control
Avoid:
- Individual developers disabling rules without discussion
- Excessive suppression comments
- Ignoring tool output
CI Integration
Run quality checks in continuous integration.
Benefits:
- Automatic enforcement: Every commit checked
- Pull request feedback: Violations shown in PR
- Quality trends: Track improvements over time
- Fail fast: Catch issues before code review
GitHub Actions example (all tools):
name: Code Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: "21"
distribution: "temurin"
cache: "maven"
- name: Checkstyle
run: mvn checkstyle:check
- name: SpotBugs
run: mvn spotbugs:check
- name: PMD
run: mvn pmd:check
- name: Tests with Coverage
run: mvn test jacoco:report
- name: SonarQube
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn sonar:sonarRelated Content
- Best Practices - Coding standards and conventions
- Build Tools - Maven and Gradle integration
- CI/CD Pipelines - Continuous integration patterns
- Security Practices - Security vulnerability detection
- Test-Driven Development - Test coverage metrics