Article
Checkstyle is a development tool that helps programmers write Java code that adheres to coding standards. It automates the process of checking Java code, saving time and ensuring consistency across the codebase. This guide covers everything from basic setup to advanced configuration patterns.
Why Use Checkstyle?
- Code Consistency: Enforces consistent coding standards across teams
- Early Bug Detection: Catches common programming errors
- Maintainability: Improves code readability and maintainability
- Automation: Integrates with build tools and CI/CD pipelines
- Customization: Highly configurable to match team preferences
Project Setup and Dependencies
Maven Dependencies:
<properties>
<checkstyle.version>10.12.5</checkstyle.version>
<maven-checkstyle-plugin.version>3.3.0</maven-checkstyle-plugin.version>
</properties>
<dependencies>
<!-- Checkstyle for programmatic use -->
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>${checkstyle.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Maven Checkstyle Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven-checkstyle-plugin.version}</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
</configuration>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Gradle Configuration:
plugins {
id 'checkstyle'
}
checkstyle {
toolVersion = '10.12.5'
configFile = file("config/checkstyle/checkstyle.xml")
showViolations = true
ignoreFailures = false
}
tasks.withType(Checkstyle) {
reports {
xml.required = false
html.required = true
html.stylesheet = resources.text.fromFile('config/checkstyle/checkstyle-style.xsl')
}
}
1. Basic Checkstyle Configuration
checkstyle.xml - Basic Configuration:
<?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">
<!-- Basic Properties -->
<property name="charset" value="UTF-8"/>
<property name="severity" value="error"/>
<!-- File Tab Character Check -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<!-- TreeWalker - processes individual Java files -->
<module name="TreeWalker">
<!-- Naming Conventions -->
<module name="ConstantName">
<property name="format" value="^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$"/>
</module>
<module name="LocalFinalVariableName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
</module>
<module name="LocalVariableName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
</module>
<module name="MemberName">
<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="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
</module>
<module name="StaticVariableName">
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
</module>
<module name="TypeName">
<property name="format" value="^[A-Z][a-zA-Z0-9]*$"/>
</module>
<!-- Imports -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/>
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<!-- Size Violations -->
<module name="MethodLength">
<property name="max" value="50"/>
<property name="countEmpty" value="false"/>
</module>
<module name="ParameterNumber">
<property name="max" value="7"/>
<property name="ignoreOverriddenMethods" value="true"/>
</module>
<!-- Whitespace -->
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
<property name="allowMultipleEmptyLines" value="false"/>
</module>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
</module>
<!-- Coding -->
<module name="EmptyBlock">
<property name="option" value="text"/>
</module>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="MultipleVariableDeclarations"/>
<module name="OneStatementPerLine"/>
<module name="StringLiteralEquality"/>
<!-- Modifiers -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Annotations -->
<module name="AnnotationLocation">
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationUseStyle"/>
<module name="MissingOverride"/>
<!-- Design -->
<module name="FinalClass"/>
<module name="InterfaceIsType"/>
<module name="VisibilityModifier">
<property name="protectedAllowed" value="true"/>
<property name="packageAllowed" value="true"/>
</module>
<!-- Javadoc -->
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="scope" value="public"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowedAnnotations" value="Override, Test"/>
</module>
<module name="JavadocType">
<property name="scope" value="public"/>
</module>
<module name="JavadocStyle">
<property name="checkFirstSentence" value="false"/>
</module>
<module name="SummaryJavadoc">
<property name="forbiddenSummaryFragments" value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
</module>
</module>
</module>
2. Advanced Checkstyle Configuration
checkstyle-advanced.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="severity" value="error"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Suppression Filter -->
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml"/>
</module>
<!-- Suppress with annotations -->
<module name="SuppressWarningsFilter"/>
<!-- File Length Check -->
<module name="FileLength">
<property name="max" value="2000"/>
</module>
<!-- Newline at end of file -->
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<!-- Unique Properties -->
<module name="UniqueProperties"/>
<!-- TreeWalker -->
<module name="TreeWalker">
<!-- Custom Checks -->
<module name="RegexpSinglelineJava">
<property name="format" value="System\.out\.println"/>
<property name="message" value="Use logger instead of System.out.println"/>
<property name="ignoreComments" value="true"/>
</module>
<module name="RegexpSinglelineJava">
<property name="format" value="System\.err\.println"/>
<property name="message" value="Use logger instead of System.err.println"/>
<property name="ignoreComments" value="true"/>
</module>
<module name="RegexpSinglelineJava">
<property name="format" value="printStackTrace\(\)"/>
<property name="message" value="Avoid using printStackTrace(), use logger instead"/>
<property name="ignoreComments" value="true"/>
</module>
<!-- Cyclomatic Complexity -->
<module name="CyclomaticComplexity">
<property name="max" value="15"/>
<property name="severity" value="warning"/>
</module>
<!-- NPath Complexity -->
<module name="NPathComplexity">
<property name="max" value="200"/>
</module>
<!-- Boolean Expression Complexity -->
<module name="BooleanExpressionComplexity">
<property name="max" value="7"/>
</module>
<!-- Class Data Abstraction Coupling -->
<module name="ClassDataAbstractionCoupling">
<property name="max" value="15"/>
</module>
<!-- Class Fan-Out Complexity -->
<module name="ClassFanOutComplexity">
<property name="max" value="20"/>
</module>
<!-- Executable Statement Count -->
<module name="ExecutableStatementCount">
<property name="max" value="30"/>
</module>
<!-- JavaNCSS (Non-Commenting Source Statements) -->
<module name="JavaNCSS">
<property name="methodMaximum" value="50"/>
<property name="classMaximum" value="1500"/>
<property name="fileMaximum" value="2000"/>
</module>
<!-- Magic Number -->
<module name="MagicNumber">
<property name="ignoreNumbers" value="-1, 0, 1, 2, 3, 4"/>
<property name="ignoreAnnotation" value="true"/>
</module>
<!-- Mutable Exception -->
<module name="MutableException"/>
<!-- Throws Count -->
<module name="ThrowsCount">
<property name="max" value="3"/>
</module>
<!-- Nested If Depth -->
<module name="NestedIfDepth">
<property name="max" value="3"/>
</module>
<!-- Nested Try Depth -->
<module name="NestedTryDepth">
<property name="max" value="2"/>
</module>
<!-- Nested For Depth -->
<module name="NestedForDepth">
<property name="max" value="3"/>
</module>
<!-- Nested While Depth -->
<module name="NestedWhileDepth">
<property name="max" value="3"/>
</module>
<!-- Return Count -->
<module name="ReturnCount">
<property name="max" value="5"/>
</module>
<!-- Illegal Instantiation -->
<module name="IllegalInstantiation">
<property name="classes" value="java.lang.Boolean, java.lang.Integer, java.lang.Long, java.lang.Double, java.lang.Float"/>
</module>
<!-- Illegal Throws -->
<module name="IllegalThrows">
<property name="illegalClassNames" value="java.lang.Throwable, java.lang.Exception, java.lang.RuntimeException, java.lang.Error"/>
</module>
<!-- Illegal Catch -->
<module name="IllegalCatch">
<property name="illegalClassNames" value="java.lang.Exception, java.lang.Throwable, java.lang.RuntimeException"/>
</module>
<!-- Inner Assignment -->
<module name="InnerAssignment"/>
<!-- Modified Control Variable -->
<module name="ModifiedControlVariable"/>
<!-- Simplify Boolean Expression -->
<module name="SimplifyBooleanExpression"/>
<!-- Simplify Boolean Return -->
<module name="SimplifyBooleanReturn"/>
<!-- String Literal Equality -->
<module name="StringLiteralEquality"/>
<!-- Nested If Depth -->
<module name="NestedIfDepth">
<property name="max" value="3"/>
</module>
<!-- Package Declaration -->
<module name="PackageDeclaration"/>
<!-- Explicit Initialization -->
<module name="ExplicitInitialization"/>
<!-- Final Parameters -->
<module name="FinalParameters">
<property name="tokens" value="CTOR_DEF, METHOD_DEF, LAMBDA"/>
</module>
<!-- Trailing Comment -->
<module name="TrailingComment"/>
<!-- Uncommented Main -->
<module name="UncommentedMain">
<property name="excludedClasses" value="Application, Main, Startup"/>
</module>
<!-- Upper Ell -->
<module name="UpperEll">
<property name="severity" value="warning"/>
</module>
<!-- Custom Import Order -->
<module name="CustomImportOrder">
<property name="customImportOrderRules" value="STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE"/>
<property name="standardPackageRegExp" value="^java\."/>
<property name="specialImportsRegExp" value="^javax\."/>
<property name="thirdPartyPackageRegExp" value="^org\.,^com\."/>
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>
</module>
<!-- Avoid Static Import -->
<module name="AvoidStaticImport">
<property name="excludes" value="java.lang.System.out, org.junit.Assert.*, org.mockito.Mockito.*, org.mockito.ArgumentMatchers.*"/>
</module>
<!-- Import Order -->
<module name="ImportOrder">
<property name="groups" value="/^java\./,javax,org,com"/>
<property name="ordered" value="true"/>
<property name="separated" value="true"/>
<property name="option" value="top"/>
<property name="sortStaticImportsAlphabetically" value="true"/>
</module>
<!-- Require This -->
<module name="RequireThis">
<property name="checkMethods" value="false"/>
<property name="validateOnlyOverlapping" value="false"/>
</module>
<!-- Hidden Field -->
<module name="HiddenField">
<property name="ignoreConstructorParameter" value="true"/>
<property name="ignoreSetter" value="true"/>
<property name="ignoreAbstractMethods" value="true"/>
</module>
<!-- Parameter Assignment -->
<module name="ParameterAssignment">
<property name="severity" value="warning"/>
</module>
<!-- Final Local Variable -->
<module name="FinalLocalVariable">
<property name="tokens" value="VARIABLE_DEF, PARAMETER_DEF"/>
<property name="validateEnhancedForLoopVariable" value="true"/>
</module>
<!-- Super Finalize -->
<module name="SuperFinalize"/>
<!-- Super Clone -->
<module name="SuperClone"/>
<!-- Javadoc Variable -->
<module name="JavadocVariable">
<property name="scope" value="protected"/>
</module>
<!-- Write Tag -->
<module name="WriteTag">
<property name="tag" value="@author"/>
<property name="tagFormat" value="\S"/>
<property name="severity" value="ignore"/>
</module>
<!-- Javadoc Paragraph -->
<module name="JavadocParagraph"/>
<!-- Single Line Javadoc -->
<module name="SingleLineJavadoc">
<property name="ignoreInlineTags" value="false"/>
</module>
<!-- NonEmptyAtclauseDescription -->
<module name="NonEmptyAtclauseDescription"/>
<!-- Javadoc Content Location -->
<module name="JavadocContentLocationCheck"/>
<!-- Comments -->
<module name="CommentsIndentation"/>
<!-- Indentation -->
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="8"/>
<property name="lineWrappingIndentation" value="8"/>
<property name="arrayInitIndent" value="4"/>
</module>
<!-- Line Length -->
<module name="LineLength">
<property name="max" value="120"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<!-- Avoid Escaped Unicode Characters -->
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<!-- Outer Type Filename -->
<module name="OuterTypeFilename"/>
<!-- Todo Comment -->
<module name="TodoComment">
<property name="severity" value="info"/>
</module>
<!-- Regexp -->
<module name="RegexpSinglelineJava">
<property name="format" value="\s+$"/>
<property name="message" value="Line has trailing spaces."/>
</module>
</module>
</module>
3. Suppression Configuration
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 generated code --> <suppress files="[/\\]generated-sources[/\\]" checks=".*"/> <suppress files="[/\\]target[/\\]" checks=".*"/> <suppress files="[/\\]build[/\\]" checks=".*"/> <!-- Suppress test code for certain checks --> <suppress files="[/\\]src[/\\]test[/\\]java[/\\]" checks="JavadocMethod"/> <suppress files="[/\\]src[/\\]test[/\\]java[/\\]" checks="JavadocType"/> <suppress files="[/\\]src[/\\]test[/\\]java[/\\]" checks="MagicNumber"/> <suppress files="[/\\]src[/\\]test[/\\]java[/\\]" checks="MethodLength"/> <!-- Suppress main method for certain checks --> <suppress files=".*[/\\]Application\.java" checks="HideUtilityClassConstructor"/> <suppress files=".*[/\\]Main\.java" checks="HideUtilityClassConstructor"/> <suppress files=".*[/\\]Startup\.java" checks="HideUtilityClassConstructor"/> <!-- Suppress configuration classes --> <suppress files=".*[/\\]config[/\\].*" checks="HideUtilityClassConstructor"/> <!-- Suppress DTO/Entity classes for certain checks --> <suppress files=".*[/\\]model[/\\].*" checks="FinalClass"/> <suppress files=".*[/\\]dto[/\\].*" checks="FinalClass"/> <suppress files=".*[/\\]entity[/\\].*" checks="FinalClass"/> <!-- Suppress specific packages --> <suppress files=".*[/\\]com[/\\]example[/\\]legacy[/\\].*" checks=".*"/> <!-- Suppress specific files --> <suppress files=".*[/\\]GeneratedCode\.java" checks=".*"/> <!-- Suppress specific checks for specific files --> <suppress files=".*[/\\]Constants\.java" checks="InterfaceIsType"/> <suppress files=".*[/\\]Constants\.java" checks="VisibilityModifier"/> <!-- Suppress line length for test assertions --> <suppress files=".*[/\\]src[/\\]test[/\\]java[/\\].*" checks="LineLength"/> <!-- Suppress method count for test classes --> <suppress files=".*[/\\]src[/\\]test[/\\]java[/\\].*" checks="MethodCount"/> </suppressions>
4. Custom Checkstyle Checks
Custom Check Implementation:
package com.example.checkstyle.checks;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import java.util.regex.Pattern;
/**
* Custom check to enforce logger naming convention.
* Loggers should be named 'log' and be private static final.
*/
public class LoggerNamingCheck extends AbstractCheck {
private static final String LOGGER_PATTERN = "^log$";
private static final String MSG_KEY = "logger.naming";
private final Pattern pattern = Pattern.compile(LOGGER_PATTERN);
@Override
public int[] getDefaultTokens() {
return new int[] { TokenTypes.VARIABLE_DEF };
}
@Override
public int[] getAcceptableTokens() {
return getDefaultTokens();
}
@Override
public int[] getRequiredTokens() {
return getDefaultTokens();
}
@Override
public void visitToken(DetailAST ast) {
final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
final String variableName = ident.getText();
// Check if this is a logger field
if (isLoggerType(type) && !pattern.matcher(variableName).matches()) {
log(ast.getLineNo(), MSG_KEY, variableName);
}
// Check modifiers
if (isLoggerType(type) && pattern.matcher(variableName).matches()) {
checkLoggerModifiers(ast);
}
}
private boolean isLoggerType(DetailAST type) {
final DetailAST ident = type.findFirstToken(TokenTypes.IDENT);
if (ident != null) {
final String typeName = ident.getText();
return "Logger".equals(typeName) || "org.slf4j.Logger".equals(typeName);
}
return false;
}
private void checkLoggerModifiers(DetailAST ast) {
final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
boolean isPrivate = modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
boolean isStatic = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
boolean isFinal = modifiers.findFirstToken(TokenTypes.FINAL) != null;
if (!isPrivate || !isStatic || !isFinal) {
log(ast.getLineNo(), "logger.modifiers", "Logger should be private static final");
}
}
}
/**
* Custom check to enforce that test methods follow naming conventions.
*/
public class TestMethodNamingCheck extends AbstractCheck {
private static final String TEST_METHOD_PATTERN = "^test[A-Z][a-zA-Z0-9]*$|^should[A-Z][a-zA-Z0-9]*$|^when[A-Z][a-zA-Z0-9]*$";
private static final String MSG_KEY = "test.method.naming";
private final Pattern pattern = Pattern.compile(TEST_METHOD_PATTERN);
@Override
public int[] getDefaultTokens() {
return new int[] { TokenTypes.METHOD_DEF };
}
@Override
public int[] getAcceptableTokens() {
return getDefaultTokens();
}
@Override
public int[] getRequiredTokens() {
return getDefaultTokens();
}
@Override
public void visitToken(DetailAST ast) {
final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
final String methodName = ident.getText();
// Check if this is in a test class
if (isInTestClass(ast) && !pattern.matcher(methodName).matches()) {
log(ast.getLineNo(), MSG_KEY, methodName);
}
}
private boolean isInTestClass(DetailAST ast) {
DetailAST parent = ast.getParent();
while (parent != null) {
if (parent.getType() == TokenTypes.CLASS_DEF) {
final DetailAST classIdent = parent.findFirstToken(TokenTypes.IDENT);
final String className = classIdent.getText();
return className.endsWith("Test") || className.endsWith("IT");
}
parent = parent.getParent();
}
return false;
}
}
Custom Check Configuration:
<!-- Add to checkstyle.xml --> <module name="TreeWalker"> <!-- Custom Checks --> <module name="com.example.checkstyle.checks.LoggerNamingCheck"/> <module name="com.example.checkstyle.checks.TestMethodNamingCheck"/> <!-- Other standard checks... --> </module>
5. IDE Integration
IntelliJ IDEA Configuration:
- Install Checkstyle Plugin:
- Go to Settings → Plugins → Marketplace
- Search for "Checkstyle-IDEA" and install
- Configure Checkstyle:
- Settings → Tools → Checkstyle
- Add configuration file:
checkstyle.xml - Set scan scope to "All sources except tests"
- Enable "Treat checkstyle errors as warnings"
- Create IntelliJ Inspection Profile:
<!-- intellij-checkstyle.xml --> <profile version="1.0"> <option name="myName" value="Checkstyle" /> <inspection_tool class="CheckStyle" enabled="true" level="WARNING" enabled_by_default="true"> <option name="configurationFile" value="$PROJECT_DIR$/checkstyle.xml" /> <option name="suppressErrors" value="false" /> </inspection_tool> </profile>
Eclipse Configuration:
- Install Checkstyle Plugin:
- Help → Eclipse Marketplace
- Search for "Checkstyle Plug-in" and install
- Configure Checkstyle:
- Window → Preferences → Checkstyle
- Add configuration file:
checkstyle.xml - Set as default for new projects
6. Maven Multi-module Configuration
Parent POM Configuration:
<!-- parent-pom.xml -->
<properties>
<checkstyle.config.location>${project.basedir}/../config/checkstyle</checkstyle.config.location>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven-checkstyle-plugin.version}</version>
<configuration>
<configLocation>${checkstyle.config.location}/checkstyle.xml</configLocation>
<suppressionsLocation>${checkstyle.config.location}/suppressions.xml</suppressionsLocation>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<failsOnError>true</failsOnError>
<consoleOutput>true</consoleOutput>
</configuration>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>${checkstyle.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
Module-specific Configuration:
<!-- module-pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<!-- Module-specific suppressions -->
<suppressionsLocation>${checkstyle.config.location}/suppressions-${project.artifactId}.xml</suppressionsLocation>
<!-- Different rules for test modules -->
<configLocation>${checkstyle.config.location}/checkstyle-${project.packaging}.xml</configLocation>
</configuration>
</plugin>
</plugins>
</build>
7. Programmatic Usage
Checkstyle Programmatic Runner:
package com.example.checkstyle;
import com.puppycrawl.tools.checkstyle.Checker;
import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
import com.puppycrawl.tools.checkstyle.PropertiesExpander;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class CheckstyleRunner {
public CheckstyleResults runCheckstyle(File configFile, List<File> sourceFiles)
throws CheckstyleException {
// Load configuration
Configuration config = ConfigurationLoader.loadConfiguration(
configFile.getAbsolutePath(),
new PropertiesExpander(System.getProperties()),
ConfigurationLoader.IgnoredModulesOptions.OMIT
);
// Create checker
Checker checker = new Checker();
checker.setModuleClassLoader(CheckstyleRunner.class.getClassLoader());
checker.configure(config);
// Add audit listener
CustomAuditListener listener = new CustomAuditListener();
checker.addListener(listener);
// Run checkstyle
int errorCount = checker.process(sourceFiles);
// Collect results
CheckstyleResults results = new CheckstyleResults();
results.setErrorCount(errorCount);
results.setViolations(listener.getViolations());
checker.destroy();
return results;
}
public static class CheckstyleResults {
private int errorCount;
private List<Violation> violations = new ArrayList<>();
// Getters and setters
public int getErrorCount() { return errorCount; }
public void setErrorCount(int errorCount) { this.errorCount = errorCount; }
public List<Violation> getViolations() { return violations; }
public void setViolations(List<Violation> violations) { this.violations = violations; }
}
public static class Violation {
private String fileName;
private int lineNumber;
private String message;
private String severity;
// Constructors, getters, and setters
public Violation(String fileName, int lineNumber, String message, String severity) {
this.fileName = fileName;
this.lineNumber = lineNumber;
this.message = message;
this.severity = severity;
}
public String getFileName() { return fileName; }
public int getLineNumber() { return lineNumber; }
public String getMessage() { return message; }
public String getSeverity() { return severity; }
}
}
8. CI/CD Integration
GitHub Actions Configuration:
# .github/workflows/checkstyle.yml name: Checkstyle on: push: branches: [ main, develop ] pull_request: branches: [ main, develop ] jobs: checkstyle: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' cache: 'maven' - name: Run Checkstyle run: mvn checkstyle:check - name: Upload Checkstyle Report uses: actions/upload-artifact@v3 if: always() with: name: checkstyle-report path: target/checkstyle-result.xml
Jenkins Pipeline:
pipeline {
agent any
stages {
stage('Checkstyle') {
steps {
sh 'mvn checkstyle:checkstyle'
publishCheckstyle pattern: '**/checkstyle-result.xml'
}
post {
always {
recordIssues(
tools: [checkStyle(pattern: '**/checkstyle-result.xml')],
qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]
)
}
}
}
}
}
9. Best Practices and Tips
Configuration Organization:
<!-- checkstyle-base.xml - Base configuration --> <module name="Checker"> <property name="severity" value="error"/> <!-- Common modules that apply to all projects --> <module name="TreeWalker"> <module name="ConstantName"/> <module name="LocalVariableName"/> <!-- ... --> </module> </module> <!-- checkstyle-extended.xml - Extended configuration --> <!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd"> <module name="Checker"> <!-- Include base configuration --> <module name="com.puppycrawl.tools.checkstyle.TreeWalker"> <property name="file" value="checkstyle-base.xml"/> </module> <!-- Project-specific extensions --> <module name="TreeWalker"> <module name="CustomImportOrder"/> <module name="MethodLength"> <property name="max" value="30"/> </module> </module> </module>
Gradual Adoption Strategy:
<!-- checkstyle-gradual.xml --> <module name="Checker"> <property name="severity" value="warning"/> <!-- Start with warnings --> <module name="TreeWalker"> <!-- Start with critical checks --> <module name="IllegalImport"/> <module name="RedundantImport"/> <module name="UnusedImports"/> <module name="EmptyBlock"/> <module name="NeedBraces"/> <!-- Gradually add more checks --> <!-- <module name="MethodLength"> <property name="max" value="50"/> <property name="severity" value="info"/> </module> --> </module> </module>
10. Common Issues and Solutions
Issue: Too many violations in legacy code
<!-- Solution: Use suppressions and gradual adoption --> <suppress files=".*[/\\]legacy[/\\].*" checks=".*"/> <suppress files=".*[/\\]generated[/\\].*" checks=".*"/>
Issue: Test code has different requirements
<!-- Solution: Separate configuration for tests -->
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions-test.xml"/>
</module>
Issue: Third-party code violations
<!-- Solution: Exclude third-party sources --> <suppress files=".*[/\\]thirdparty[/\\].*" checks=".*"/>
Conclusion
Checkstyle is a powerful tool for maintaining code quality and consistency in Java projects. Key benefits include:
- Consistency Enforcement: Ensures all team members follow the same coding standards
- Early Issue Detection: Catches common issues before code review
- Automation: Integrates seamlessly with build tools and CI/CD pipelines
- Customization: Highly configurable to match team preferences
- Educational: Helps developers learn and follow best practices
By implementing the configurations and patterns shown above, you can create a robust Checkstyle setup that improves code quality while being flexible enough to accommodate different project needs and team preferences.