| name | java |
| description | [Applies to: **/*] Definitive guidelines for writing high-quality, maintainable, and performant Java code, adhering to modern best practices (Java 21/25) and enterprise standards like Google Java Style. |
| source | cursor_mdc |
Java Best Practices
This document outlines the definitive best practices for Java development within our team. Adherence to these rules ensures consistent, high-quality, and maintainable code, leveraging modern Java features and enterprise standards.
1. Code Organization and Structure
1.1. Style Guide Adherence
Always follow the Google Java Style Guide for formatting, naming, and structural conventions. Configure your IDE (e.g., IntelliJ IDEA, VS Code with appropriate plugins) to automatically apply this style.
1.2. File Naming and Encoding
Source files must be named after the single top-level class they contain, with a .java extension. All source files must use UTF-8 encoding.
1.3. Indentation and Whitespace
Use 4 spaces for indentation. Never use tabs. For wrapped lines, use 8 spaces (twice the normal indentation).
❌ BAD
public class MyClass {
void myMethod() {
System.out.println("Hello");
}
}
✅ GOOD
public class MyClass {
void myMethod() {
System.out.println("Hello");
}
}
1.4. Braces
Always use K&R style braces for non-empty blocks. Braces are mandatory for if, else, for, do, and while statements, even for single-line bodies.
❌ BAD
if (condition)
doSomething();
while (true){
doSomething();
}
✅ GOOD
if (condition) {
doSomething();
}
while (true) {
doSomethingElse();
}
1.5. Package and Import Statements
Every class must belong to a package. Use explicit imports; wildcard imports (*) are forbidden. Imports are ordered: static imports, then non-static imports, separated by a single blank line, both groups in ASCII sort order.
❌ BAD
import java.util.*;
import static com.example.Constants.*;
public class MyClass { }
✅ GOOD
import static com.example.Constants.DEFAULT_VALUE;
import java.util.ArrayList;
import java.util.List;
public class MyClass { }
1.6. Naming Conventions
Adhere strictly to these conventions:
- Packages:
com.yourcompany.project.module (all lowercase, dot-separated).
- Classes/Enums/Records/Interfaces:
PascalCase (nouns).
- Variables/Parameters:
camelCase.
- Constants:
SCREAMING_SNAKE_CASE (for static final fields).
- Methods:
camelCase (verbs).
- Boolean Variables/Methods: Prefix with
is, has, can, should.
- Collections: Use plural names (e.g.,
users, messages).
- Test Methods:
featureUnderTest_testScenario_expectedBehavior().
❌ BAD
public class userManager {
public static final String default_user_name = "guest";
private List userList;
public boolean checkUser(User u) { }
}
✅ GOOD
package com.example.app.users;
import java.util.List;
public class UserManager {
public static final String DEFAULT_USER_NAME = "guest";
private List<User> users;
public boolean isValidUser(User user) { }
}
2. Common Patterns and Anti-patterns
2.1. Prefer Immutability
Design classes to be immutable whenever possible. This simplifies concurrency and reasoning about state. Use record classes (Java 16+) for simple immutable data carriers.
❌ BAD
public class User {
private String name;
public User(String name) { this.name = name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
✅ GOOD
public record User(String name) {}
public final class ImmutableUser {
private final String name;
public ImmutableUser(String name) { this.name = name; }
public String getName() { return name; }
}
2.2. Embrace Functional Programming with Streams and Optionals
Leverage Java's Stream API for collection processing and Optional for handling potentially absent values.
❌ BAD
List<String> names = getUsers().stream()
.filter(u -> u.getAge() > 18)
.map(User::getName)
.collect(Collectors.toList());
User user = findUserById(123L);
if (user != null) {
System.out.println(user.getName());
} else {
System.out.println("User not found");
}
✅ GOOD
List<String> names = users.stream()
.filter(user -> user.getAge() > 18)
.map(User::getName)
.toList();
findUserById(123L)
.map(User::getName)
.ifPresentOrElse(
System.out::println,
() -> System.out.println("User not found")
);
2.3. Resource Management with Try-with-resources
Always use try-with-resources for any resource that implements AutoCloseable.
❌ BAD
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
✅ GOOD
try (FileInputStream fis = new FileInputStream("file.txt")) {
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
}
3. Performance Considerations
3.1. Efficient String Concatenation
Use StringBuilder for concatenating multiple strings in loops or when building complex strings.
❌ BAD
String result = "";
for (int i = 0; i < 1000; i++) {
result += i;
}
✅ GOOD
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString();
3.2. Logging Performance
Avoid expensive string formatting or method calls in logging statements unless the log level is enabled. Use parameterized logging.
❌ BAD
logger.debug("Processing user: " + user.getName() + " with ID: " + user.getId());
✅ GOOD
logger.debug("Processing user: {} with ID: {}", user.getName(), user.getId());
4. Common Pitfalls and Gotchas
4.1. Avoid Mutable Static State
Mutable static fields are a common source of concurrency bugs and make testing difficult. Minimize their use.
❌ BAD
public class Counter {
public static int count = 0;
public static void increment() { count++; }
}
✅ GOOD
public class CounterService {
private int count = 0;
public synchronized void increment() { count++; }
public int getCount() { return count; }
}
4.2. Dependency Version Management
Always pin dependency versions. Use Bill-of-Materials (BOMs) for managing compatible versions across related libraries (e.g., Spring Boot BOM, Google Cloud Libraries BOM).
❌ BAD (Maven)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
✅ GOOD (Maven with BOM)
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
4.3. Correct equals() and hashCode() Implementation
Always override both equals() and hashCode() together, or neither. Use Objects.equals() and Objects.hash() for robust implementations. For data classes, record types handle this automatically.
❌ BAD
public class Point {
private int x, y;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
}
✅ GOOD
import java.util.Objects;
public final class Point {
private final int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return