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/whilebody. - 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’sforbidWildcardImportsstep. Expand wildcards to one explicit import per used symbol — this includes static imports (e.g. writeimport static org.mockito.Mockito.when;, notimport static org.mockito.Mockito.*;). - No unused imports. Spotless’s
removeUnusedImportsdeletes them onapply. - Standard import order. Spotless’s
importOrder(default ordering) is applied automatically. Do not hand-tune the order; letspotless:applyrewrite 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 (
finalkeyword)
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
Optionalfor 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
- DRY (Don’t Repeat Yourself) - Extract common code
- KISS (Keep It Simple) - Avoid over-engineering
- YAGNI (You Aren’t Gonna Need It) - Don’t add unnecessary features
- Favor composition over inheritance
- Program to interfaces, not implementations
- Make classes immutable where possible
- Minimize class and member accessibility