Style GuideCoding Conventions

Java Coding Conventions

Formatting

All whitespace, indentation, line wrapping, and brace placement are handled by Google Java Format via Spotless — mvn spotless:apply rewrites your code into the canonical form, and mvn spotless:check (bound to mvn verify) fails the build on any deviation.

You do not need to memorise the rules. For reference, Google Java Format produces:

  • 2-space block indent, 4-space continuation indent.
  • 100-character soft line limit.
  • K&R braces; mandatory braces around every if / else / for / while body.
  • Imports sorted into a fixed order with no wildcard imports (also enforced by forbidWildcardImports).

If spotless:apply rewrites your code in a way that surprises you, that is the convention — run it before committing rather than fighting the formatter.

A pre-push hook is available via mvn spotless:install-git-pre-push-hook.

Imports

Imports are managed and enforced by Spotless (mvn spotless:apply to fix, mvn spotless:check / mvn verify to validate).

Enforced rules

  • No wildcard imports. import …*; is rejected at build time by Spotless’s forbidWildcardImports step. Expand wildcards to one explicit import per used symbol — this includes static imports (e.g. write import static org.mockito.Mockito.when;, not import static org.mockito.Mockito.*;).
  • No unused imports. Spotless’s removeUnusedImports deletes them on apply.
  • Standard import order. Spotless’s importOrder (default ordering) is applied automatically. Do not hand-tune the order; let spotless:apply rewrite it.

License header

Every Java source file must start with the LGPL-3.0 header block. The canonical text lives in license-header.txt at the repository root and is enforced by Spotless’s licenseHeader rule. mvn spotless:apply adds the header to any file missing it.

Annotations

Entity Annotations

@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class User extends AbstractBaseModel {
  // fields
}

Service Annotations

@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class UserService {
  private final UserRepository repository;
  // methods
}

Controller Annotations

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Tag(name = "User Management")
public class UserController {
  private final UserService service;
  // endpoints
}

Constants and Variables

Constants

  • Use UPPER_SNAKE_CASE
  • Declare as public static final
  • Group related constants together
public static final String DEFAULT_ROLE = "USER";
public static final int MAX_LOGIN_ATTEMPTS = 3;
public static final long TOKEN_VALIDITY = 3600L;

Variables

  • Use meaningful names
  • Avoid single-letter names except for loop indices
  • Prefer immutability (final keyword)

Method Conventions

Parameters

  • Limit to 3-4 parameters
  • Use builder pattern for many parameters
  • Validate input with @NotNull, @Valid
public User createUser(
    @NotNull @Valid UserDto dto,
    @NotNull Role defaultRole) {
  // implementation
}

Return Types

  • Use Optional for nullable returns
  • Return empty collections instead of null
  • Use specific types over generic ones

Comments and Documentation

Javadoc

/**
 * Creates a new user with the given data.
 *
 * @param dto the user data transfer object
 * @param role the initial role to assign
 * @return the created user entity
 * @throws UserAlreadyExistsException if email is already taken
 */
public User createUser(UserDto dto, Role role) {
  // implementation
}

Inline Comments

  • Use sparingly, prefer self-documenting code
  • Explain why, not what
  • Keep comments up-to-date

SQL and JPQL

Named Queries

@Query("""
  SELECT u FROM User u
  WHERE u.email = :email
    AND u.active = true
  """)
Optional<User> findActiveByEmail(@Param("email") String email);

Native Queries

@Query(
  value = """
    SELECT * FROM users u
    WHERE u.created_at > :date
    ORDER BY u.last_name, u.first_name
    """,
  nativeQuery = true)
List<User> findRecentUsers(@Param("date") LocalDateTime date);

Exception Handling

Custom Exceptions

public class UserNotFoundException extends EntityNotFoundException {
  public UserNotFoundException(Long id) {
    super(String.format("User with id %d not found", id));
  }
}

Exception Handler

@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleUserNotFound(UserNotFoundException ex) {
  return ErrorResponse.of(ex.getMessage());
}

Best Practices

  1. DRY (Don’t Repeat Yourself) - Extract common code
  2. KISS (Keep It Simple) - Avoid over-engineering
  3. YAGNI (You Aren’t Gonna Need It) - Don’t add unnecessary features
  4. Favor composition over inheritance
  5. Program to interfaces, not implementations
  6. Make classes immutable where possible
  7. Minimize class and member accessibility