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:

  1. Package declaration
  2. Imports (grouped and sorted)
  3. File-level documentation
  4. File-level constants
  5. Main class with members in order:
    • Properties
    • Init blocks
    • Public functions
    • Private functions
    • Companion object
  6. Extension functions
  7. 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

🧠 Test Your Knowledge

What naming convention should be used for Kotlin class names?