This document outlines best practices for writing secure, reliable, and efficient Java code.
- Minimize the scope of variables.
- Minimize the scope of the
@SuppressWarningsannotation. - Minimize the accessibility of classes and their members.
- Document thread-safety and use annotations where applicable.
- Always provide feedback about the resulting value of a method.
- Identify files using multiple file attributes.
- Do not attach significance to the ordinal associated with an
enum. - Be aware of numeric promotion behavior.
- Enable compile-time type checking of variable arity parameter types.
- Do not apply
public finalto constants whose value might change in later releases. - Avoid cyclic dependencies between packages.
- Prefer user-defined exceptions over more general exception types.
- Try to gracefully recover from system errors.
- Carefully design interfaces before releasing them.
- Write garbage collection–friendly code.
- Do not shadow or obscure identifiers in subscopes.
- Do not declare more than one variable per declaration.
- Use meaningful symbolic constants to represent literal values in program logic.
- Properly encode relationships in constant definitions.
- Return an empty array or collection instead of a
nullvalue for methods that return an array or collection. - Use exceptions only for exceptional conditions.
- Use a try-with-resources statement to safely handle closeable resources.
- Do not use assertions to verify the absence of runtime errors.
- Use the same type for the second and third operands in conditional expressions.
- Do not serialize direct handles to system resources.
- Prefer using iterators over enumerations.
- Do not use direct buffers for short-lived, infrequently used objects.
- Remove short-lived objects from long-lived container objects.
- Be careful using visually misleading identifiers and literals.
- Avoid ambiguous overloading of variable arity methods.
- Avoid in-band error indicators.
- Do not perform assignments in conditional expressions.
- Use braces for the body of an
if,for, orwhilestatement. - Do not place a semicolon immediately following an
if,for, orwhilecondition. - Finish every set of statements associated with a
caselabel with a break statement. - Avoid inadvertent wrapping of loop counters.
- Use parentheses for precedence of operation.
- Do not make assumptions about file creation.
- Convert integers to floating-point for floating-point operations.
- Ensure that the
clone()method callssuper.clone(). - Use comments consistently and in a readable fashion.
- Detect and remove superfluous code and values.
- Strive for logical completeness.
- Avoid ambiguous or confusing uses of overloading.
- Do not assume that declaring a reference
volatileguarantees safe publication of the members of the referenced object. - Do not assume that the
sleep(),yield(), orgetState()methods provide synchronization semantics. - Do not assume that the remainder operator always returns a nonnegative result for integral operands.
- Do not confuse abstract object equality with reference equality.
- Understand the differences between bitwise and logical operators.
- Understand how escape characters are interpreted when strings are loaded.
- Do not use overloaded methods to differentiate between runtime types.
- Never confuse the immutability of a reference with that of the referenced object.
- Use the serialization methods
writeUnshared()andreadUnshared()with care. - Do not attempt to help the garbage collector by setting local reference variables to
null.
- Static factory methods provide named constructors, making the code easier to read and use.
- They allow control over instance creation, enabling instance reuse or returning instances of subclasses.
- Classes without public constructors cannot be subclassed, encouraging immutability.
- Use common naming conventions like
from,of,valueOf,getInstance,newInstance.
- Telescoping constructors are hard to manage with many parameters.
- Use a builder pattern to handle optional parameters while maintaining readability and consistency.
- Builders are particularly effective for immutable objects and hierarchical class structures.
- Use a private constructor with a static instance field or an enum type to enforce a single instance.
- Enum types are more concise and handle serialization correctly.
- Use a private constructor for utility classes to prevent instantiation.
- The compiler generates a default public constructor unless explicitly defined.
- Dependency injection increases flexibility and testability.
- Avoid hard-coded dependencies; use frameworks like Spring or Guice for injection.
- Reuse immutable objects whenever possible.
- Avoid instantiating objects unnecessarily in loops.
- Nullify object references to eliminate memory leaks.
- Use tools like
try-with-resourcesto manage resources effectively.
- Finalizers and cleaners are unpredictable and can cause performance issues.
- Use
try-with-resourcesor explicit resource management.
try-with-resourcesensures proper resource closure and is less error-prone.- Use for classes implementing
AutoCloseable.
- Maintain consistency, reflexivity, symmetry, transitivity, and non-nullity in equality checks.
- Failing to do so violates the general contract for
hashCodeand causes issues in hash-based collections.
- Provide a human-readable string representation of the object for debugging and logging.
- Use
Cloneablesparingly due to its inconsistent behavior.
- Implement
Comparablefor natural ordering of objects in collections.
- Use the lowest possible access level.
- Prefer private or package-private over public unless necessary.
- Encapsulation ensures maintainability and flexibility in your code.
- Immutable classes are simpler, safer, and thread-safe by default.
- Make all fields final and private.
- Inheritance violates encapsulation; use composition and delegation instead.
- Document the inheritance behavior to ensure subclassing is safe and predictable.
- Interfaces are more flexible and allow for multiple inheritance.
- Anticipate future changes to avoid breaking clients.
- Avoid using interfaces to define constants.
- Replace tagged classes with proper hierarchies for better clarity and maintainability.
- Nonstatic member classes hold an implicit reference to the outer class and can lead to memory leaks.
- Improves readability and reduces errors.
- Raw types lose type safety and can result in runtime errors.
- Suppress warnings with
@SuppressWarningsand ensure type safety.
- Generics work better with collections than arrays due to type mismatch issues.
- Generic types improve readability and reusability.
- Generic methods allow for flexible and type-safe methods.
- Use
? extendsand? superfor producer/consumer generics.
- Ensure type safety when using both generics and varargs.
- Use typesafe containers for flexibility, e.g.,
Class<T>as keys.
- Enums are more readable, type-safe, and maintainable.
- Ordinals can lead to errors; use fields to associate data with enums.
EnumSetis more flexible and type-safe.
EnumMapprovides efficient and type-safe mappings for enums.
- Interfaces allow extending enums indirectly.
- Annotations are more robust and readable than naming conventions.
- Prevents errors by ensuring methods override superclass methods.
- Marker interfaces are cleaner than marker annotations.
- Lambdas improve code readability and conciseness.
- Method references simplify and clarify the intent when calling existing methods.
- Use standard interfaces like
Function,Consumer, andPredicatefor consistency.
- Streams improve readability for bulk data operations but can hinder performance if overused.
- Side effects in streams can lead to unpredictable behavior and bugs.
- Collections are more versatile and reusable than streams.
- Parallel streams may not always improve performance and can lead to complexity.
- Validate input parameters to avoid unexpected behavior or security vulnerabilities.
- Use
Objects.requireNonNullfor null checks.
- Protect mutable objects from being modified unexpectedly by making defensive copies.
- Choose clear, concise names, and prioritize consistency in method signatures.
- Avoid overloading methods with similar parameter types that may confuse clients.
- Use varargs for methods with zero or more parameters, but avoid performance overhead for frequently called methods.
- Returning null forces clients to perform null checks, which increases complexity.
- Use
Optionalas a return type for methods that might not return a value, but avoid overusing it.
- Clearly document the behavior and expectations of public API methods.
- Declare variables as close as possible to their first use.
- This improves readability and reduces error risk.
- Use for-each loops for cleaner and more readable iteration when applicable.
- Leverage standard libraries to avoid reinventing the wheel and ensure reliability.
- Use
BigDecimal,int, orlongfor precise calculations, especially in financial applications.
- Primitive types are more efficient than their wrapper classes.
- Use enums, collections, or other appropriate data types instead of strings to represent complex data.
- Use
StringBuilderorStringBufferfor string concatenation inside loops.
- Use interface types for variables, return types, and parameters for flexibility.
- Reflection compromises type safety and maintainability.
- Use native methods only when critical performance gains are necessary.
- Avoid premature optimization; measure and profile before optimizing.
- Follow established naming conventions for readability and consistency.
- Avoid using exceptions for control flow or predictable conditions.
Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
- Checked exceptions indicate recoverable conditions, while runtime exceptions signal errors in code logic.
- Use checked exceptions only when they provide clear benefits.
- Use standard exceptions like
IllegalArgumentExceptionandNullPointerExceptionfor clarity.
- Ensure exceptions align with the level of abstraction of the method or class.
- Specify exceptions in Javadoc comments to inform API users.
- Provide context for the failure in exception messages to aid debugging.
- Ensure that failed methods leave objects in a consistent state.
- Always handle exceptions explicitly, even if it means logging them.
- Use synchronized blocks or locks to prevent data races and inconsistent states.
- Over-synchronization can lead to deadlocks and reduce performance.
- Use higher-level concurrency abstractions like
ExecutorServicefor better scalability.
- Use
java.util.concurrentutilities for simpler and more robust concurrent programming.
- Clearly specify whether your class is thread-safe, not thread-safe, or conditionally thread-safe.
- Lazy initialization can improve performance but should be used only when necessary.
- Avoid relying on thread priorities or scheduling behavior for program correctness.
- Serialization is fragile and exposes security risks; use alternatives like JSON or protocol buffers.
- Serialization can lead to compatibility issues and security vulnerabilities.
- Customize serialization for better performance and reduced storage requirements.
- Validate inputs during deserialization to avoid corrupting objects.
- Enums simplify instance control and ensure correctness.
- Proxies provide a more robust and secure way to serialize complex objects.
These best practices help ensure code quality, maintainability, and performance. Adopting these principles leads to more robust, readable, and efficient Java programs.