google-site-verification: google61fe8ba583a51912.html
Error Prone: Catch Bugs at Compile Time in Java

Error Prone is a static analysis tool for Java that catches common programming mistakes at compile time. It extends the Java compiler to identify bugs, anti-patterns, and suspicious code constructs that regular compilers might miss.


What is Error Prone?

Error Prone is a Java compiler plugin that performs additional compile-time checks beyond standard Java compilation. It was developed at Google and is used extensively in their Java codebase to prevent bugs before they reach production.

Key Benefits:

  • Compile-time bug detection - Catch issues before runtime
  • Zero runtime overhead - Analysis happens during compilation
  • Extensible - Create custom checks
  • IDE integration - Works with popular IDEs
  • Build tool support - Maven, Gradle, Bazel

How Error Prone Works

Java Source → javac + Error Prone → Compilation Errors/Warnings ↓ Additional Bug Checks

Getting Started

Maven Configuration

<project> <properties> <errorprone.version>2.18.0</errorprone.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>11</source> <target>11</target> <encoding>UTF-8</encoding> <compilerArgs> <arg>-XDcompilePolicy=simple</arg> <arg>-Xplugin:ErrorProne</arg> </compilerArgs> <annotationProcessorPaths> <path> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_core</artifactId> <version>${errorprone.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build> <dependencies> <!-- Error Prone annotations --> <dependency> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_annotations</artifactId> <version>${errorprone.version}</version> </dependency> </dependencies> </project>

Gradle Configuration

plugins { id 'java' id 'net.ltgt.errorprone' version '3.0.1' } repositories { mavenCentral() } dependencies { errorprone 'com.google.errorprone:error_prone_core:2.18.0' implementation 'com.google.errorprone:error_prone_annotations:2.18.0' } tasks.withType(JavaCompile) { options.compilerArgs += [ '-Xplugin:ErrorProne', '-XDcompilePolicy=simple' ] }

Common Error Prone Checks with Examples

1. ArrayEquals - Incorrect array comparison

import java.util.Arrays; public class ArrayEqualsBug { public static void main(String[] args) { int[] a = {1, 2, 3}; int[] b = {1, 2, 3}; // BUG: Using == for array comparison if (a == b) { // Error Prone will catch this! System.out.println("Arrays are equal"); } // CORRECT: Use Arrays.equals() if (Arrays.equals(a, b)) { System.out.println("Arrays are equal"); } } }

Error Prone Output:

error: [ArrayEquals] Reference equality used to compare arrays if (a == b) { ^ (see https://errorprone.info/bugpattern/ArrayEquals)

2. StringEquals - Incorrect string comparison

public class StringEqualsBug { public boolean checkString(String input) { // BUG: Using == for string comparison return input == "expected"; // Error Prone will catch this! } public boolean checkStringCorrect(String input) { // CORRECT: Use equals() return "expected".equals(input); } public boolean checkNullableString(String input) { // BUG: Potential NPE return input.equals("expected"); // Error Prone suggests better pattern // CORRECT: Put the literal first // return "expected".equals(input); } }

3. MissingOverride - Missing @Override annotation

interface Animal { void makeSound(); String getName(); } public class Dog implements Animal { // BUG: Missing @Override annotation public void makeSound() { // Error Prone will warn System.out.println("Woof!"); } // CORRECT: With @Override @Override public String getName() { return "Dog"; } } class BaseClass { public void importantMethod() { System.out.println("Base implementation"); } } class DerivedClass extends BaseClass { // BUG: Typo in method name, but no compilation error public void importantMethod() { // Error Prone catches the missing @Override System.out.println("Derived implementation"); } }

4. MutableConstant - Mutable objects as constants

import java.util.Date; import java.util.ArrayList; import java.util.List; public class MutableConstants { // BUG: Date is mutable public static final Date CREATION_DATE = new Date(); // Error Prone warning // BUG: ArrayList is mutable public static final List<String> NAMES = new ArrayList<>(); // Error Prone warning // CORRECT: Use immutable alternatives public static final String CREATION_DATE_STRING = "2024-01-01"; public static final List<String> IMMUTABLE_NAMES = List.of("Alice", "Bob"); }

5. BoxedPrimitiveConstructor - Deprecated boxed primitive constructors

public class BoxedPrimitiveBug { public void problematicCode() { // BUG: Using deprecated constructors Integer number = new Integer(42); // Error Prone warning Boolean flag = new Boolean(true); // Error Prone warning Long bigNumber = new Long(123L); // Error Prone warning // CORRECT: Use valueOf() or autoboxing Integer goodNumber = Integer.valueOf(42); Boolean goodFlag = Boolean.TRUE; Long goodBigNumber = 123L; // autoboxing } }

6. DeadException - Exceptions that are immediately thrown

public class DeadExceptionExample { public void validateInput(String input) { // BUG: Exception created and immediately thrown if (input == null) { throw new IllegalArgumentException("Input cannot be null"); // Error Prone may warn about style } // BUG: Unnecessarily creating exception objects IllegalArgumentException ex = new IllegalArgumentException(); if (input.isEmpty()) { throw ex; // Better to create when needed } } // CORRECT: Create exception only when needed public void betterValidateInput(String input) { if (input == null) { throw new IllegalArgumentException("Input cannot be null"); } if (input.isEmpty()) { throw new IllegalArgumentException("Input cannot be empty"); } } }

7. FutureReturnValueIgnored - Ignoring Future return values

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class FutureIgnoredBug { private final ExecutorService executor = Executors.newSingleThreadExecutor(); public void processTask() { // BUG: Ignoring Future return value executor.submit(() -> doWork()); // Error Prone warning // CORRECT: Handle the Future Future<?> future = executor.submit(() -> doWork()); // Can now check cancellation, exceptions, etc. } private void doWork() { // Some long-running task } }

8. StreamResource - Unclosed streams

import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; public class StreamResourceBug { public void readFileProblematic(String filename) throws IOException { // BUG: Stream not closed in all paths FileInputStream fis = new FileInputStream(filename); // Error Prone warning if (someCondition()) { return; // Stream leaked! } fis.close(); } public void readFileCorrect(String filename) throws IOException { // CORRECT: Use try-with-resources try (FileInputStream fis = new FileInputStream(filename)) { // Use the stream } } public void modernJavaRead(String filename) throws IOException { // CORRECT: Using Files API Files.readAllBytes(Paths.get(filename)); } private boolean someCondition() { return Math.random() > 0.5; } }

Advanced Error Prone Usage

Custom Error Prone Checks

import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher; import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.Matcher; import com.google.errorprone.matchers.Matchers; import com.sun.source.tree.MethodTree; // Custom Error Prone check to enforce naming conventions @BugPattern( name = "LoggerNamingConvention", summary = "Logger instances should be named 'log' or 'logger'", severity = BugPattern.SeverityLevel.WARNING, tags = BugPattern.StandardTags.STYLE ) public class LoggerNamingCheck extends BugChecker implements MethodTreeMatcher { private static final Matcher<MethodTree> LOGGER_FIELD = Matchers.isField() .and(Matchers.isSubtypeOf("org.slf4j.Logger")) .and(Matchers.not(Matchers.anyOf( Matchers.hasName("log"), Matchers.hasName("logger") ))); @Override public Description matchMethod(MethodTree tree, VisitorState state) { if (LOGGER_FIELD.matches(tree, state)) { return describeMatch(tree); } return Description.NO_MATCH; } }

Using Error Prone Annotations

import com.google.errorprone.annotations.Immutable; import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Var; @Immutable // Error Prone will check that all fields are final public final class ImmutablePoint { private final double x; private final double y; public ImmutablePoint(double x, double y) { this.x = x; this.y = y; } @CheckReturnValue // Error if return value is ignored public ImmutablePoint translate(double dx, double dy) { return new ImmutablePoint(x + dx, y + dy); } public void process() { @Var int count = 0; // @Var documents that variable is mutated count++; System.out.println(count); } }

Configuration and Customization

Error Prone Flags Configuration

Maven configuration with custom flags:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>11</source> <target>11</target> <compilerArgs> <arg>-XDcompilePolicy=simple</arg> <arg>-Xplugin:ErrorProne -Xep:StringEquals:WARN -Xep:MissingOverride:ERROR -Xep:DeadException:OFF</arg> </compilerArgs> <annotationProcessorPaths> <path> <groupId>com.google.errorprone</groupId> <artifactId>error_prone_core</artifactId> <version>2.18.0</version> </path> </annotationProcessorPaths> </configuration> </plugin>

Error Prone Suppression

import com.google.errorprone.annotations.SuppressWarnings; public class SuppressionExample { @SuppressWarnings("ArrayEquals") // Suppress specific check public boolean compareArrays(int[] a, int[] b) { // We have a good reason to use reference equality here return a == b; } @SuppressWarnings({"StringEquals", "MissingOverride"}) // Suppress multiple checks public void multipleSuppressions() { String s = "test"; if (s == "test") { // Intentional reference comparison // ... } } // Use standard @SuppressWarnings for some checks @java.lang.SuppressWarnings("FutureReturnValueIgnored") public void ignoreFuture() { executor.submit(() -> doWork()); // We don't care about the Future } }

Integration with Build Tools

GitHub Actions with Error Prone

name: Java CI with Error Prone on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' - name: Build with Maven and Error Prone run: mvn compile -X - name: Run Error Prone explicitly run: mvn compile -Dmaven.compiler.failOnWarning=true

Real-World Examples

Example: Comprehensive Error Prone Usage

import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Immutable; import java.util.List; import java.util.Objects; @Immutable public final class Person { private final String name; private final int age; private final List<String> emails; public Person(String name, int age, List<String> emails) { this.name = Objects.requireNonNull(name, "Name cannot be null"); this.age = age; this.emails = List.copyOf(Objects.requireNonNull(emails, "Emails cannot be null")); } @CheckReturnValue public Person withName(String newName) { return new Person(newName, age, emails); } @CheckReturnValue public Person withAge(int newAge) { return new Person(name, newAge, emails); } // Proper equals and hashCode @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Person)) return false; Person person = (Person) o; return age == person.age && name.equals(person.name) && emails.equals(person.emails); } @Override public int hashCode() { return Objects.hash(name, age, emails); } // Proper toString @Override public String toString() { return String.format("Person{name='%s', age=%d, emails=%s}", name, age, emails); } }

Best Practices

  1. Start Gradually
  • Enable a few checks first, then add more
  • Use WARN level before ERROR
  1. Team Adoption
  • Educate team about Error Prone benefits
  • Create custom checks for team-specific patterns
  1. CI Integration
  • Fail builds on Error Prone errors
  • Use in pull request checks
  1. Suppression Strategy
  • Use @SuppressWarnings sparingly
  • Document why suppression is needed
  1. Custom Checks
  • Create team-specific checks
  • Share custom checks across projects

Common Error Prone Bug Patterns

CategoryBug PatternDescription
EqualityArrayEquals, StringEqualsIncorrect reference comparisons
AnnotationsMissingOverrideMissing @Override annotations
CollectionsMutableConstantMutable objects used as constants
BoxingBoxedPrimitiveConstructorDeprecated boxed type constructors
ResourcesStreamResourceLeakUnclosed streams and resources
ConcurrencyFutureReturnValueIgnoredIgnoring Future results
NullnessNullTernarySuspicious null checks

Conclusion

Error Prone is a powerful tool that significantly improves Java code quality by:

Key Benefits:

  • Early Bug Detection - Catch issues at compile time
  • Code Quality - Enforce best practices automatically
  • Team Consistency - Standardize code patterns
  • Reduced Debugging - Fewer runtime exceptions
  • Educational - Learn better coding practices

When to Use Error Prone:

  • Large codebases needing consistency
  • Teams wanting to enforce coding standards
  • Projects with strict quality requirements
  • Continuous integration pipelines

Integration Points:

  • Compile-time - As part of regular compilation
  • IDEs - Plugin for immediate feedback
  • CI/CD - Fail builds on errors
  • Code Review - Pre-commit checks

By integrating Error Prone into your development workflow, you can catch many common Java bugs before they ever reach production, saving time and improving code reliability.


Next Steps: Start by enabling a few critical checks in your build, gradually add more patterns as your team becomes comfortable, and consider creating custom checks for your specific domain patterns and team conventions.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper