Java Best Practices
Writing code that works is only the first step. Writing code that is maintainable, readable, and performant is what distinguishes a senior Java developer. This guide covers industry-standard best practices for Java development.
1. Naming Conventions and Code Style
Consistent naming makes code self-documenting.
- Classes & Interfaces: PascalCase (e.g.,
CustomerAccount,Runnable). - Methods & Variables: camelCase (e.g.,
calculateTotal(),firstName). - Constants: UPPER_SNAKE_CASE with the
static finalmodifiers (e.g.,MAX_RETRY_COUNT). - Packages: all-lowercase, usually starting with a top-level domain (e.g.,
com.companyname.project). - Meaningful Names: Avoid single-letter variables (except in short loops like
i,j). UseemployeeListinstead ofel.
2. Object-Oriented Design (SOLID Principles)
Java is inherently object-oriented. Applying SOLID principles ensures your codebase is flexible and robust.
- Single Responsibility Principle (SRP): A class should have one, and only one, reason to change. Separate UI logic, business logic, and database access.
- Open/Closed Principle (OCP): Classes should be open for extension but closed for modification. Use interfaces and abstract classes instead of repeatedly modifying existing concrete classes.
- Liskov Substitution Principle (LSP): Subclasses should be substitutable for their base classes without breaking the program.
- Interface Segregation Principle (ISP): Many client-specific interfaces are better than one general-purpose interface.
- Dependency Inversion Principle (DIP): Depend on abstractions, not concretions. Use Dependency Injection (DI) frameworks like Spring.
3. Handling Nulls Properly
NullPointerException (NPE) is the most common runtime error in Java.
- Use
Optional: Introduced in Java 8,Optional<T>is an excellent way to indicate that a method might return no value. Do not useOptionalas a parameter type or class field; it is designed for return types. - Avoid Returning Null: Return empty collections (
Collections.emptyList()) or empty strings instead ofnullwhen a collection or string is expected. - Fail Fast: Validate method arguments at the beginning of the method. Use
Objects.requireNonNull().
public void processUser(User user) {
Objects.requireNonNull(user, "User cannot be null");
// process...
}
4. Exception Handling
- Catch Specific Exceptions: Avoid catching generic
ExceptionorThrowable. Catch the most specific exception possible. - Don't Swallow Exceptions: Never leave a catch block empty. At minimum, log the exception.
- Use Try-With-Resources: Always clean up resources (Files, Streams, Database connections). Instead of using a
finallyblock, use the try-with-resources statement (introduced in Java 7) for any class implementingAutoCloseable.
// Best Practice: Try-with-resources
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
return br.readLine();
} catch (IOException e) {
log.error("Failed to read file", e);
throw new CustomException("File read error", e);
}
5. Strings and Performance
- Use
StringBuilderfor Concatenation: SinceStringis immutable, concatenating strings in a loop using+creates many intermediate objects, wasting memory and CPU. UseStringBuilder(orStringBufferif thread-safety is required, though rare). - String
equals(): When comparing a known literal to a variable, call.equals()on the literal to avoid NPEs.// Bad if (status.equals("ACTIVE")) { ... } // status might be null // Good if ("ACTIVE".equals(status)) { ... }
6. Collections Framework
- Program to Interfaces: Always use interfaces for variable types, not implementations.
List<String> list = new ArrayList<>(); // Good ArrayList<String> list = new ArrayList<>(); // Bad - Size Collections Appropriately: If you know the approximate number of elements, initialize collections with an initial capacity to avoid expensive resizing operations.
List<User> users = new ArrayList<>(100); - Use the Right Collection:
ArrayListfor fast reads and iteration.LinkedListif you are mostly adding/removing from the ends.HashSetfor uniqueness and fast lookup.TreeSetwhen you need sorted uniqueness.
7. Concurrency
- Avoid Manual Thread Creation: Do not use
new Thread(Runnable).start(). Use theExecutorServiceframework to manage thread pools. - Prefer
java.util.concurrent: Use concurrent data structures (ConcurrentHashMap,CopyOnWriteArrayList) instead of synchronizing standard collections. - Immutability: The best way to make a class thread-safe is to make it immutable. Mark fields as
final, do not provide setters, and defensively copy mutable objects passed into constructors or returned from getters.
8. General Tips
- Use Annotations: Utilize
@Overrideto catch method signature errors at compile time. - Avoid Magic Numbers: Extract arbitrary numbers into named constants.
- Write Unit Tests: Code without tests is legacy code by default. Aim for high test coverage focusing on business logic using JUnit and Mockito.