Kotlin Style Guide
Best practices for writing clean and readable Kotlin code
📝 What is a Style Guide?
A style guide provides consistent formatting and naming conventions for writing clean, readable Kotlin code. Following these practices makes your code easier to understand, maintain, and collaborate on with other developers.
// Good style - clear and consistent
class UserService(private val repository: UserRepository) {
fun findUserById(id: Long): User? {
return repository.findById(id)
}
}
Style Categories
Naming
Consistent naming conventions
class UserService
val userName = "john"
fun calculateTotal()
Formatting
Code layout and indentation
if (condition) {
doSomething()
}
Documentation
Comments and KDoc
/**
* Calculates user age
*/
fun calculateAge(): Int
Structure
File and class organization
// Imports
// Class declaration
// Properties
// Functions
🔹 Naming Conventions
Use consistent naming patterns for different code elements:
// Classes and interfaces - PascalCase
class UserService
interface PaymentProcessor
data class OrderItem(val id: Long, val name: String)
// Functions and variables - camelCase
fun calculateTotalPrice(items: List<Item>): Double {
val taxRate = 0.08
var subtotal = 0.0
for (item in items) {
subtotal += item.price
}
return subtotal * (1 + taxRate)
}
// Constants - SCREAMING_SNAKE_CASE
const val MAX_RETRY_ATTEMPTS = 3
const val DEFAULT_TIMEOUT_SECONDS = 30
// Package names - lowercase with dots
package com.example.userservice.repository
// File names - PascalCase matching main class
// UserService.kt, PaymentProcessor.kt
// Boolean properties - use "is" prefix when appropriate
val isActive: Boolean = true
val hasPermission: Boolean = false
val canEdit: Boolean = user.role == Role.ADMIN
Naming Guidelines:
- Classes/Interfaces: PascalCase (UserService, PaymentProcessor)
- Functions/Variables: camelCase (calculateTotal, userName)
- Constants: SCREAMING_SNAKE_CASE (MAX_SIZE)
- Packages: lowercase.with.dots
🔹 Code Formatting
Consistent formatting makes code easier to read:
// Indentation - 4 spaces (not tabs)
class OrderService(
private val orderRepository: OrderRepository,
private val paymentService: PaymentService,
private val emailService: EmailService
) {
// Function formatting
fun processOrder(
customerId: Long,
items: List<OrderItem>,
shippingAddress: Address
): Order {
// Local variables
val customer = customerService.findById(customerId)
?: throw CustomerNotFoundException("Customer $customerId not found")
val order = Order(
customerId = customerId,
items = items,
shippingAddress = shippingAddress,
status = OrderStatus.PENDING
)
// Control flow formatting
if (items.isEmpty()) {
throw IllegalArgumentException("Order must contain at least one item")
}
// Chain calls formatting
val totalPrice = items
.filter { it.isAvailable }
.map { it.price * it.quantity }
.reduce { acc, price -> acc + price }
// When expression formatting
val shippingCost = when {
totalPrice > 100.0 -> 0.0
totalPrice > 50.0 -> 5.0
else -> 10.0
}
return orderRepository.save(order)
}
}
Formatting Rules:
- Indentation: 4 spaces, no tabs
- Line length: Maximum 120 characters
- Braces: Opening brace on same line
- Spacing: Space after keywords, around operators
🔹 Documentation and Comments
Write clear documentation using KDoc and comments:
/**
* Service for managing user accounts and authentication.
*
* This service provides methods for user registration, login,
* password management, and profile updates.
*
* @property userRepository Repository for user data access
* @property passwordEncoder Service for password encryption
* @since 1.0.0
*/
class UserService(
private val userRepository: UserRepository,
private val passwordEncoder: PasswordEncoder
) {
/**
* Registers a new user account.
*
* @param email User's email address (must be unique)
* @param password Plain text password (will be encrypted)
* @param fullName User's full name
* @return The created user account
* @throws UserAlreadyExistsException if email is already registered
* @throws InvalidEmailException if email format is invalid
*/
fun registerUser(
email: String,
password: String,
fullName: String
): User {
// Validate email format
if (!isValidEmail(email)) {
throw InvalidEmailException("Invalid email format: $email")
}
// Check if user already exists
if (userRepository.existsByEmail(email)) {
throw UserAlreadyExistsException("User with email $email already exists")
}
// Create and save user
val encryptedPassword = passwordEncoder.encode(password)
val user = User(
email = email,
password = encryptedPassword,
fullName = fullName,
createdAt = Instant.now()
)
return userRepository.save(user)
}
/**
* Validates email format using regex pattern.
*
* @param email Email to validate
* @return true if email format is valid
*/
private fun isValidEmail(email: String): Boolean {
// Simple email validation - in production, use a proper library
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
return email.matches(emailRegex.toRegex())
}
}
Documentation Guidelines:
- KDoc: Use /** */ for public APIs
- Parameters: Document with @param
- Return values: Document with @return
- Exceptions: Document with @throws
- Comments: Explain why, not what
🔹 File and Class Organization
Organize your code files and classes consistently:
// File: UserService.kt
package com.example.service
// 1. Imports - grouped and sorted
import com.example.model.User
import com.example.repository.UserRepository
import com.example.exception.UserNotFoundException
import java.time.Instant
import java.util.UUID
// 2. File-level documentation
/**
* User management service implementation.
*/
// 3. Constants (if any)
private const val MAX_LOGIN_ATTEMPTS = 5
// 4. Main class
class UserService(
private val userRepository: UserRepository
) {
// 4a. Properties (grouped by visibility)
private val loginAttempts = mutableMapOf<String, Int>()
// 4b. Init blocks (if needed)
init {
// Initialization code
}
// 4c. Public functions
fun findUserById(id: UUID): User? {
return userRepository.findById(id)
}
fun createUser(email: String, name: String): User {
val user = User(
id = UUID.randomUUID(),
email = email,
name = name,
createdAt = Instant.now()
)
return userRepository.save(user)
}
// 4d. Private functions
private fun validateEmail(email: String): Boolean {
return email.contains("@") && email.contains(".")
}
// 4e. Companion object (if needed)
companion object {
const val DEFAULT_PAGE_SIZE = 20
fun createDefaultService(): UserService {
return UserService(InMemoryUserRepository())
}
}
}
// 5. Extension functions (if any)
fun User.isActive(): Boolean = this.status == UserStatus.ACTIVE
// 6. Data classes and enums (if small and related)
enum class UserStatus {
ACTIVE, INACTIVE, SUSPENDED
}
File Organization Order:
- Package declaration
- Imports (grouped and sorted)
- File-level documentation
- File-level constants
-
Main class with members in order:
- Properties
- Init blocks
- Public functions
- Private functions
- Companion object
- Extension functions
- Related small classes/enums
🔹 Best Practices Summary
Key principles for writing excellent Kotlin code:
✅ Do:
- Use meaningful, descriptive names
- Keep functions small and focused
- Use data classes for simple data containers
- Prefer immutable variables (val over var)
- Use null safety features (?., ?:, !!)
- Write unit tests for your code
- Use Kotlin idioms (let, apply, run, etc.)
❌ Don't:
- Use abbreviations in names (usr instead of user)
- Write functions longer than 20-30 lines
- Ignore null safety warnings
- Use !! operator without good reason
- Mix tabs and spaces for indentation
- Leave TODO comments in production code
- Catch generic Exception without handling