- UTF-8 encoding: All source files must be encoded in UTF-8.
- File naming: File name must match the top-level class name exactly, plus .java extension.
- Single top-level class: Each source file contains exactly one top-level class.
- File organization: Files must follow this exact order:
- License or copyright information (if present)
- Package statement
- Import statements
- Exactly one top-level class declaration
- Blank line separation: Exactly one blank line separates each section that is present.
- Package naming: Use only lowercase letters and digits, no underscores. Concatenate words directly:
com.example.deepspace
, notcom.example.deep_space
. - No wildcard imports: Never use wildcard imports (
import java.util.*
). - Import organization:
- All static imports in a single block
- All non-static imports in a single block
- Single blank line separates the two blocks if both present
- Within each block, imports appear in ASCII sort order
- No static import for classes: Don't use static import for static nested classes.
- No line-wrapping: Import statements are never line-wrapped.
- Explicit types: Always declare explicit types, avoid raw types.
- Generic type safety: Always use parameterized types, avoid raw types.
- Array declarations: Use
String[] args
, notString args[]
(no C-style). - One variable per declaration: Declare only one variable per statement:
int a; int b;
notint a, b;
. - Variable scope: Declare local variables close to their first use, not at the start of blocks.
- Generic type naming: Use single capital letters (T, E, K, V) or class-style names followed by T (RequestT, ResponseT).
- No code repetition: Extract repeated logic into methods or utility classes.
- Descriptive naming: Use clear, descriptive names that explain purpose.
- Single responsibility: Classes and methods should have single, focused purposes.
- Fail early: Validate inputs at method entry points with clear exceptions.
- @Override annotation: Always use @Override when overriding methods or implementing interface methods.
- No ignored exceptions: Never have empty catch blocks without explanation.
-
Guard Clauses: Use guard clauses at method entry to validate preconditions
public void processOrder(Order order) { // Fail fast with clear error messages if (order == null) { throw new IllegalArgumentException("Order cannot be null"); } if (order.getItems().isEmpty()) { throw new IllegalStateException("Order must contain at least one item"); } if (order.getTotalAmount() <= 0) { throw new IllegalArgumentException("Order amount must be positive"); } // Main logic here, no nested ifs needed }
-
Early Returns: Return early from methods to reduce nesting
public Optional<Customer> findCustomer(String id) { if (id == null || id.isBlank()) { return Optional.empty(); } Customer customer = repository.findById(id); if (customer == null) { return Optional.empty(); } return Optional.of(customer); }
-
Null Object Pattern: Consider null object pattern over null checks
-
Validation Order: Validate in order of: null checks → type checks → business rules
-
Constructor Validation: Validate all invariants in constructors
-
Use Objects.requireNonNull(): For non-null validation with custom messages
- Method naming: Use lowerCamelCase, typically verbs or verb phrases (sendMessage, calculateTotal).
- Class naming: Use UpperCamelCase, typically nouns or noun phrases (Customer, PaymentProcessor).
- Method overloading: Keep overloaded methods together with no other members between them.
- Constructor placement: Place constructors before other methods.
- Class member ordering: Use logical ordering that maintainer can explain (not chronological).
- Access modifier ordering: Follow Java Language Specification order:
public protected private abstract default static final sealed non-sealed transient volatile synchronized native strictfp
- Method length: Prefer methods under 20 lines
- Class cohesion: Fields should be used by multiple methods
- Single entry, single exit: Avoid multiple returns when it hurts readability
- Immutable objects: Prefer immutable objects when possible. Use final fields and no setters.
- Builder pattern: Use for complex object construction with many optional parameters.
- Collection interfaces: Program to interfaces (
List<String>
) not implementations (ArrayList<String>
). - Optional usage: Use
Optional<T>
for return types that may be absent, avoid for parameters. - Defensive copying: Make defensive copies of mutable objects when necessary.
- Null-safe collections: Return empty collections, never null
- Collection copying: Always return unmodifiable views or copies
-
Layer Independence: Each layer should only know about the layer directly below it
- Controllers → Services → Repositories → Database
- Never skip layers or create circular dependencies
-
Interface Segregation:
- Define minimal interfaces for each concern
- Clients should not depend on methods they don't use
// Good: Segregated interfaces interface OrderReader { Order findById(String id); } interface OrderWriter { void save(Order order); } // Bad: Fat interface interface OrderRepository { Order findById(String id); void save(Order order); List<Order> findByCustomer(String customerId); void delete(String id); // ... many more methods }
-
Dependency Injection:
- Inject dependencies, don't create them
- Use constructor injection for required dependencies
- Use interfaces for dependencies, not concrete classes
-
Domain Logic Isolation:
- Keep business logic in service/domain layer
- Controllers handle HTTP concerns only
- Repositories handle data access only
- DTOs for external communication, entities for business logic
-
Cross-Cutting Concerns:
- Use aspects or interceptors for logging, security, transactions
- Don't scatter these concerns throughout business logic
- Specific exceptions: Catch specific exceptions rather than generic Exception.
- Exception documentation: Document all checked exceptions with @throws.
- Resource management: Use try-with-resources for autocloseable resources.
- Exception naming: Exception classes should end with "Exception".
- Exception messages: Provide clear, actionable error messages.
- Exception chaining: Preserve original exception information when rethrowing.
-
Logger Declaration:
private static final Logger log = LoggerFactory.getLogger(ClassName.class);
-
Log Levels Usage:
- ERROR: System failures requiring immediate attention
- WARN: Recoverable issues or deprecated usage
- INFO: Important business events (order placed, payment processed)
- DEBUG: Detailed flow information for troubleshooting
- TRACE: Very detailed information (method entry/exit, full payloads)
-
Structured Logging with Context:
// Use MDC for correlation MDC.put("correlationId", correlationId); MDC.put("userId", userId); MDC.put("orderId", orderId); log.info("Order processing started", kv("orderId", orderId), kv("customerId", customerId), kv("amount", order.getAmount()));
-
What to Log:
- Entry/exit of key business operations
- All exceptions with context
- External system calls (with duration)
- Security events (authentication, authorization)
- Configuration changes
- Performance metrics
-
What NOT to Log:
- Sensitive data (passwords, credit cards, PII)
- Entire request/response bodies at INFO level
- Loop iterations (unless TRACE level)
-
Exception Logging Pattern:
try { processPayment(order); } catch (PaymentException e) { log.error("Payment processing failed", kv("orderId", order.getId()), kv("amount", order.getAmount()), kv("errorCode", e.getErrorCode()), e); // Always include exception as last parameter throw new OrderProcessingException("Payment failed", e); }
-
Performance Logging:
@Timed public Order processOrder(OrderRequest request) { String correlationId = UUID.randomUUID().toString(); MDC.put("correlationId", correlationId); long startTime = System.currentTimeMillis(); log.info("Order processing started", kv("correlationId", correlationId)); try { Order order = orderService.process(request); log.info("Order processing completed", kv("correlationId", correlationId), kv("duration", System.currentTimeMillis() - startTime), kv("orderId", order.getId())); return order; } finally { MDC.clear(); } }
-
Correlation & Tracing:
- Generate correlation IDs at entry points
- Pass correlation IDs through all layers
- Include in all log messages and external calls
- Use distributed tracing (OpenTelemetry, Zipkin)
-
Log Message Guidelines:
- Start with action/event ("Order created", "Payment failed")
- Use consistent terminology
- Include relevant IDs and business context
- Make messages searchable and parseable
- Avoid string concatenation, use parameterized messages
-
Braces: Use K&R style (Egyptian brackets) for all blocks:
if (condition) { doSomething(); } else { doSomethingElse(); }
-
Empty blocks: May be concise
{}
except in multi-block statements. -
Indentation: Use +2 spaces for each new block level.
-
Line length: Maximum 100 characters per line.
-
One statement per line: Each statement on its own line.
-
Line wrapping: Break at higher syntactic levels, continuation lines +4 spaces.
-
Horizontal spacing:
- Space after keywords:
if (condition)
,for (int i = 0; ...)
- Space around binary operators:
x + y
,a == b
- Space after commas, semicolons, and closing parentheses in casts
- Space between type and variable:
List<String> names
- No space inside parentheses:
method(arg)
notmethod( arg )
- Space before opening braces:
class Foo {
- Space after keywords:
-
Vertical spacing:
- Single blank line between consecutive class members (fields, constructors, methods)
- Blank lines to organize code into logical subsections
- No multiple consecutive blank lines (discouraged)
- Classes: UpperCamelCase (CustomerService, PaymentProcessor)
- Methods: lowerCamelCase (calculateTotal, sendMessage)
- Variables: lowerCamelCase (customerName, totalAmount)
- Constants: UPPER_SNAKE_CASE (MAX_SIZE, DEFAULT_TIMEOUT)
- Packages: lowercase, no underscores (com.company.product)
- Test classes: End with Test (CustomerServiceTest)
- Test methods: lowerCamelCase, may use underscores (transferMoney_deductsFromSource)
Constants are static final fields whose contents are deeply immutable:
// Constants
static final int MAX_SIZE = 100;
static final List<String> VALID_CODES = ImmutableList.of("A", "B", "C");
static final Joiner COMMA_JOINER = Joiner.on(','); // Joiner is immutable
// Not constants
static String nonFinal = "mutable";
final String nonStatic = "non-static";
static final List<String> mutableList = new ArrayList<>();
static final Logger logger = Logger.getLogger(MyClass.getName());
- Exhaustiveness: Every switch must be exhaustive, add default cases even if empty.
- Modern syntax: Prefer new-style switches with arrows (->) over colons (:) when possible:
return switch (day) { case MONDAY, TUESDAY -> "Workday"; case SATURDAY, SUNDAY -> "Weekend"; default -> "Midweek"; };
- Fall-through comments: In old-style switches, mark intentional fall-through with
// fall through
. - Switch expressions: Must use new-style syntax with arrows.
- Type-use annotations: Place immediately before the annotated type:
@Nullable String name
- Class annotations: Each on separate line after documentation block
- Method annotations: Each on separate line, except single parameterless may share line
- Field annotations: Multiple annotations may share line if space permits
- Standard annotations: Use @Override, @Deprecated, @SuppressWarnings appropriately
- Javadoc required: All public classes, methods, and fields must have Javadoc.
- Javadoc format:
/** * Brief summary fragment starting with verb or noun phrase. * * @param paramName description of parameter * @return description of return value * @throws ExceptionType when this exception is thrown */
- Summary fragments: Start with noun or verb phrase, not complete sentence
- Block tags order: @param, @return, @throws, @deprecated
- Implementation comments: Use
//
for single-line,/* */
for multi-line - TODO comments: Format as
// TODO: context - explanation
with specific timeline
- Enum formatting: Each constant on its own line if methods present:
public enum Status { ACTIVE { @Override public boolean isProcessable() { return true; } }, INACTIVE, PENDING; public abstract boolean isProcessable(); }
- Simple enums: May be formatted on single line if no methods:
enum Color { RED, GREEN, BLUE }
- Static member access: Qualify with class name, not instance:
Math.max(a, b)
- String comparison: Use
.equals()
method, not==
- Numeric literals: Use uppercase L for long literals:
3000000000L
- No finalizers: Never override
Object.finalize()
- Utility classes: Make constructor private and class final
- Builder pattern: For objects with many optional parameters
- Immutable collections: Use Guava's ImmutableList, ImmutableMap, etc.
- Immutable classes: Final class, final fields, no setters, defensive copying
- Value objects: Implement equals(), hashCode(), toString()
- Service classes: Stateless, injectable dependencies
- Repository interfaces: Abstract data access concerns
- DTO/Data classes: Simple data containers with validation
- Package organization: Group related classes in logical packages by feature/domain
- Layer separation:
com.company.product.controller
- REST controllers/presentationcom.company.product.service
- Business logiccom.company.product.repository
- Data accesscom.company.product.dto
- Data transfer objectscom.company.product.entity
- Domain entitiescom.company.product.exception
- Custom exceptionscom.company.product.config
- Configuration classescom.company.product.util
- Utility classes (minimize these)com.company.product.validation
- Validation logiccom.company.product.mapper
- Object mapping/transformation
- Dependency direction: Higher-level packages should not depend on lower-level implementation details
- Test organization: Mirror main source structure in test directories
- Checked exceptions: For recoverable conditions that caller must handle
- Runtime exceptions: For programming errors and unrecoverable conditions
- Exception hierarchy: Create specific exception types for different error conditions
- Resource cleanup: Always use try-with-resources for AutoCloseable resources
- Exception wrapping: Wrap lower-level exceptions with domain-specific exceptions
- Test class naming: ClassNameTest for classes, FeatureIntegrationTest for integration
- Test method naming: Use descriptive names, underscores allowed for readability
- Test organization: Given-When-Then structure in test methods
- Mock usage: Mock external dependencies, test behavior not implementation
- Test data: Use builders or factories for complex test object creation