Packages and Imports: Organizing Your Code

Introduction

As your Scala applications grow, organizing code becomes crucial for maintainability, collaboration, and modularity. Packages provide a way to group related classes and objects into namespaces, while imports allow you to use code from different packages. Understanding how to structure packages and manage imports effectively is essential for building real-world Scala applications.

This lesson will teach you how to create well-organized package structures, use imports efficiently, manage visibility, and create modular architectures that scale with your application's complexity.

Package Declaration and Structure

Basic Package Declaration

// File: src/main/scala/com/company/myapp/models/User.scala
package com.company.myapp.models

case class User(id: Long, username: String, email: String, isActive: Boolean = true) {
  def displayName: String = s"$username ($email)"
}

object User {
  def create(username: String, email: String): Either[String, User] = {
    if (username.length < 3) {
      Left("Username must be at least 3 characters")
    } else if (!email.contains("@")) {
      Left("Invalid email format")
    } else {
      Right(User(System.currentTimeMillis(), username, email))
    }
  }
}

// File: src/main/scala/com/company/myapp/models/Product.scala
package com.company.myapp.models

case class Product(
  id: String,
  name: String,
  price: BigDecimal,
  category: String,
  inStock: Boolean = true
) {
  def isExpensive: Boolean = price > 100
  def formattedPrice: String = f"$$${price}%.2f"
}

// File: src/main/scala/com/company/myapp/services/UserService.scala
package com.company.myapp.services

import com.company.myapp.models.User

class UserService {
  private var users: Map[Long, User] = Map.empty

  def createUser(username: String, email: String): Either[String, User] = {
    User.create(username, email) match {
      case Right(user) =>
        users = users + (user.id -> user)
        Right(user)
      case Left(error) => Left(error)
    }
  }

  def findUser(id: Long): Option[User] = users.get(id)

  def findByUsername(username: String): Option[User] = {
    users.values.find(_.username == username)
  }

  def listActiveUsers(): List[User] = {
    users.values.filter(_.isActive).toList
  }
}

Nested Packages

// File: src/main/scala/com/company/myapp/data/repositories/UserRepository.scala
package com.company.myapp.data.repositories

import com.company.myapp.models.User

trait UserRepository {
  def save(user: User): Either[String, User]
  def findById(id: Long): Option[User]
  def findByEmail(email: String): Option[User]
  def delete(id: Long): Boolean
  def list(): List[User]
}

// File: src/main/scala/com/company/myapp/data/repositories/impl/InMemoryUserRepository.scala
package com.company.myapp.data.repositories.impl

import com.company.myapp.data.repositories.UserRepository
import com.company.myapp.models.User

class InMemoryUserRepository extends UserRepository {
  private var users: Map[Long, User] = Map.empty

  def save(user: User): Either[String, User] = {
    users = users + (user.id -> user)
    Right(user)
  }

  def findById(id: Long): Option[User] = users.get(id)

  def findByEmail(email: String): Option[User] = {
    users.values.find(_.email == email)
  }

  def delete(id: Long): Boolean = {
    if (users.contains(id)) {
      users = users - id
      true
    } else false
  }

  def list(): List[User] = users.values.toList
}

// File: src/main/scala/com/company/myapp/data/repositories/impl/DatabaseUserRepository.scala
package com.company.myapp.data.repositories.impl

import com.company.myapp.data.repositories.UserRepository
import com.company.myapp.models.User

class DatabaseUserRepository extends UserRepository {
  def save(user: User): Either[String, User] = {
    // Database save logic
    println(s"Saving user to database: ${user.username}")
    Right(user)
  }

  def findById(id: Long): Option[User] = {
    // Database query logic
    println(s"Querying database for user ID: $id")
    None  // Simplified for example
  }

  def findByEmail(email: String): Option[User] = {
    // Database query logic
    println(s"Querying database for user email: $email")
    None  // Simplified for example
  }

  def delete(id: Long): Boolean = {
    // Database delete logic
    println(s"Deleting user from database: $id")
    true
  }

  def list(): List[User] = {
    // Database list query
    println("Listing all users from database")
    List.empty  // Simplified for example
  }
}

Import Statements

Basic Imports

// File: src/main/scala/com/company/myapp/controllers/UserController.scala
package com.company.myapp.controllers

// Import specific class
import com.company.myapp.models.User

// Import multiple classes from same package
import com.company.myapp.services.{UserService, ProductService}

// Import all classes from a package
import com.company.myapp.data.repositories._

// Import with alias
import com.company.myapp.data.repositories.impl.{InMemoryUserRepository => MemoryRepo}

// Import Java classes
import java.time.LocalDateTime
import java.util.{UUID, Optional}

// Import Scala collections
import scala.collection.mutable
import scala.util.{Try, Success, Failure}

class UserController {
  private val userService = new UserService()
  private val userRepo: UserRepository = new MemoryRepo()  // Using alias

  def createUser(username: String, email: String): Either[String, User] = {
    val result = userService.createUser(username, email)
    result match {
      case Right(user) => 
        userRepo.save(user)
        Right(user)
      case Left(error) => Left(error)
    }
  }

  def getUser(id: Long): Option[User] = {
    userRepo.findById(id)
  }

  def generateReport(): String = {
    val users = userRepo.list()
    val timestamp = LocalDateTime.now()
    val reportId = UUID.randomUUID().toString

    s"""User Report
       |ID: $reportId
       |Generated: $timestamp
       |Total Users: ${users.length}
       |Active Users: ${users.count(_.isActive)}
       |""".stripMargin
  }
}

Selective Imports and Exclusions

package com.company.myapp.utils

// Import everything except specific items
import java.util.{List => _, Map => _, _}  // Import all except List and Map

// Import with renaming to avoid conflicts
import scala.collection.{mutable => mut, immutable => imm}

// Import specific members from an object
import scala.math.{Pi, sqrt, pow}

// Import nested packages
import com.company.myapp.data.repositories.impl._

object MathUtils {
  // Using renamed imports
  def calculateArea(radius: Double): Double = Pi * pow(radius, 2)

  def distance(x1: Double, y1: Double, x2: Double, y2: Double): Double = {
    sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2))
  }

  // Using mutable collections with alias
  def createMutableSet[T](items: T*): mut.Set[T] = {
    val set = mut.Set.empty[T]
    items.foreach(set.add)
    set
  }

  // Using immutable collections with alias
  def createImmutableList[T](items: T*): imm.List[T] = {
    imm.List(items: _*)
  }
}

// File-level imports (outside of classes/objects)
import com.company.myapp.models._
import com.company.myapp.services._

object ApplicationMain {
  def main(args: Array[String]): Unit = {
    val userService = new UserService()

    userService.createUser("alice", "alice@example.com") match {
      case Right(user) => println(s"Created user: ${user.displayName}")
      case Left(error) => println(s"Error: $error")
    }
  }
}

Import Scope and Positioning

package com.company.myapp.advanced

class DatabaseManager {
  // Imports can be placed inside classes/methods for limited scope
  import java.sql.{Connection, DriverManager, PreparedStatement}
  import scala.util.Using

  private val url = "jdbc:h2:mem:testdb"

  def executeQuery(sql: String): List[Map[String, Any]] = {
    // Import specific to this method
    import scala.collection.mutable.ListBuffer

    val results = ListBuffer.empty[Map[String, Any]]

    Using(DriverManager.getConnection(url)) { conn =>
      Using(conn.prepareStatement(sql)) { stmt =>
        val rs = stmt.executeQuery()
        while (rs.next()) {
          // Method-scoped import
          import scala.collection.mutable.Map as MutableMap
          val row = MutableMap.empty[String, Any]

          val metaData = rs.getMetaData
          for (i <- 1 to metaData.getColumnCount) {
            row(metaData.getColumnName(i)) = rs.getObject(i)
          }
          results += row.toMap
        }
      }
    }

    results.toList
  }

  def withTransaction[T](block: Connection => T): T = {
    // Block-scoped imports
    import java.sql.SQLException

    Using(DriverManager.getConnection(url)) { conn =>
      try {
        conn.setAutoCommit(false)
        val result = block(conn)
        conn.commit()
        result
      } catch {
        case _: SQLException =>
          conn.rollback()
          throw new RuntimeException("Transaction failed")
      }
    }.get
  }
}

Package Objects

Creating Package Objects

// File: src/main/scala/com/company/myapp/utils/package.scala
package com.company.myapp

// Package object - provides utilities for the entire package
package object utils {
  // Type aliases
  type UserId = Long
  type ProductId = String
  type Price = BigDecimal

  // Constants
  val DefaultPageSize: Int = 20
  val MaxPageSize: Int = 100
  val ApiVersion: String = "v1"

  // Utility functions available throughout com.company.myapp.utils package
  def formatCurrency(amount: Price): String = f"$$${amount}%.2f"

  def generateId(): String = java.util.UUID.randomUUID().toString

  def isValidEmail(email: String): Boolean = {
    val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$$"""
    email.matches(emailRegex)
  }

  def paginate[T](items: List[T], page: Int, pageSize: Int = DefaultPageSize): (List[T], Int) = {
    val safePageSize = math.min(pageSize, MaxPageSize)
    val offset = (page - 1) * safePageSize
    val pageItems = items.slice(offset, offset + safePageSize)
    val totalPages = math.ceil(items.length.toDouble / safePageSize).toInt
    (pageItems, totalPages)
  }

  // Implicit conversions
  implicit class StringExtensions(s: String) {
    def slugify: String = s.toLowerCase.replaceAll("[^a-z0-9]+", "-").stripPrefix("-").stripSuffix("-")
    def truncate(maxLength: Int): String = if (s.length <= maxLength) s else s.take(maxLength - 3) + "..."
  }

  implicit class PriceExtensions(price: Price) {
    def formatted: String = formatCurrency(price)
    def withTax(taxRate: Double = 0.1): Price = price * (1 + taxRate)
  }
}

// File: src/main/scala/com/company/myapp/models/package.scala
package com.company.myapp

package object models {
  // Domain-specific type aliases
  type UserEmail = String
  type Username = String

  // Validation functions
  def validateUsername(username: Username): Either[String, Username] = {
    if (username.length < 3) Left("Username must be at least 3 characters")
    else if (username.length > 50) Left("Username must be at most 50 characters")
    else if (!username.matches("[a-zA-Z0-9_]+")) Left("Username can only contain letters, numbers, and underscores")
    else Right(username)
  }

  def validateEmail(email: UserEmail): Either[String, UserEmail] = {
    if (email.contains("@") && email.length > 5) Right(email)
    else Left("Invalid email format")
  }

  // Status enumeration
  object UserStatus extends Enumeration {
    type UserStatus = Value
    val Active, Inactive, Suspended, Deleted = Value
  }
}

// Usage of package objects
// File: src/main/scala/com/company/myapp/services/ProductService.scala
package com.company.myapp.services

import com.company.myapp.models._
import com.company.myapp.utils._  // Imports everything from package object

class ProductService {
  private var products: Map[ProductId, Product] = Map.empty

  def createProduct(name: String, price: Price, category: String): Product = {
    val product = Product(
      id = generateId(),  // From utils package object
      name = name,
      price = price,
      category = category
    )
    products = products + (product.id -> product)
    product
  }

  def listProducts(page: Int = 1): (List[Product], Int) = {
    val productList = products.values.toList
    paginate(productList, page)  // From utils package object
  }

  def formatPrice(productId: ProductId): Option[String] = {
    products.get(productId).map(_.price.formatted)  // Using implicit extension
  }

  def generateSlug(name: String): String = {
    name.slugify  // Using implicit string extension
  }
}

Advanced Package Organization

// File: src/main/scala/com/company/myapp/config/package.scala
package com.company.myapp

package object config {
  // Configuration case classes
  case class DatabaseConfig(
    host: String,
    port: Int,
    database: String,
    username: String,
    password: String,
    maxConnections: Int = 10
  )

  case class ServerConfig(
    host: String = "localhost",
    port: Int = 8080,
    ssl: Boolean = false
  )

  case class AppConfig(
    server: ServerConfig,
    database: DatabaseConfig,
    environment: String = "development"
  ) {
    def isProduction: Boolean = environment == "production"
    def isDevelopment: Boolean = environment == "development"
  }

  // Configuration loading
  object ConfigLoader {
    def load(): AppConfig = {
      // In real app, this would load from application.conf, environment variables, etc.
      AppConfig(
        server = ServerConfig(),
        database = DatabaseConfig(
          host = sys.env.getOrElse("DB_HOST", "localhost"),
          port = sys.env.get("DB_PORT").map(_.toInt).getOrElse(5432),
          database = sys.env.getOrElse("DB_NAME", "myapp"),
          username = sys.env.getOrElse("DB_USER", "user"),
          password = sys.env.getOrElse("DB_PASS", "password")
        ),
        environment = sys.env.getOrElse("ENV", "development")
      )
    }
  }
}

// File: src/main/scala/com/company/myapp/api/package.scala
package com.company.myapp

package object api {
  // API response types
  case class ApiResponse[T](
    data: Option[T] = None,
    error: Option[String] = None,
    timestamp: java.time.LocalDateTime = java.time.LocalDateTime.now()
  )

  case class PaginatedResponse[T](
    data: List[T],
    page: Int,
    totalPages: Int,
    totalItems: Int
  )

  // HTTP status codes
  object HttpStatus {
    val OK = 200
    val CREATED = 201
    val BAD_REQUEST = 400
    val NOT_FOUND = 404
    val INTERNAL_SERVER_ERROR = 500
  }

  // Helper functions
  def success[T](data: T): ApiResponse[T] = ApiResponse(data = Some(data))
  def error(message: String): ApiResponse[Nothing] = ApiResponse(error = Some(message))

  def paginated[T](items: List[T], page: Int, totalPages: Int): PaginatedResponse[T] = {
    PaginatedResponse(items, page, totalPages, items.length)
  }

  // Request validation
  implicit class RequestValidation(request: Map[String, String]) {
    def getRequired(key: String): Either[String, String] = {
      request.get(key).toRight(s"Missing required parameter: $key")
    }

    def getOptional(key: String): Option[String] = request.get(key)

    def getInt(key: String): Either[String, Int] = {
      request.get(key) match {
        case Some(value) =>
          try Right(value.toInt)
          catch { case _: NumberFormatException => Left(s"Invalid integer value for $key: $value") }
        case None => Left(s"Missing required parameter: $key")
      }
    }
  }
}

Visibility and Access Modifiers

Package-Private and Protected Access

package com.company.myapp.security

// Base class with different access levels
abstract class SecurityContext {
  // Public - accessible from anywhere
  val sessionId: String = java.util.UUID.randomUUID().toString

  // Package-private - accessible within com.company.myapp.security package
  private[security] val internalToken: String = generateToken()

  // Protected - accessible in subclasses
  protected val secretKey: String = "secret-key-123"

  // Private - only accessible within this class
  private val internalState: String = "internal"

  // Protected method
  protected def generateToken(): String = {
    s"token_${System.currentTimeMillis()}_${scala.util.Random.alphanumeric.take(10).mkString}"
  }

  // Package-private method
  private[security] def reset(): Unit = {
    println(s"Resetting security context: $sessionId")
  }

  // Public abstract method
  def authenticate(credentials: Map[String, String]): Boolean
}

class UserSecurityContext(userId: Long) extends SecurityContext {
  // Can access protected members
  private val userToken = generateToken()  // Using protected method
  private val key = secretKey              // Using protected field

  def authenticate(credentials: Map[String, String]): Boolean = {
    // Implementation using protected members
    credentials.get("token").contains(userToken)
  }

  // Package-private method
  private[security] def getUserId: Long = userId
}

class AdminSecurityContext extends SecurityContext {
  // Admin-specific implementation
  def authenticate(credentials: Map[String, String]): Boolean = {
    credentials.get("role").contains("admin") && 
    credentials.get("key").contains(secretKey)  // Using protected field
  }

  // Can access package-private members
  def debugInfo(): String = {
    s"Session: $sessionId, Token: $internalToken"  // Using package-private field
  }
}

// Another class in the same package
class SecurityManager {
  private var contexts: Map[String, SecurityContext] = Map.empty

  def createUserContext(userId: Long): UserSecurityContext = {
    val context = new UserSecurityContext(userId)
    contexts = contexts + (context.sessionId -> context)

    // Can access package-private members
    println(s"Internal token: ${context.internalToken}")  // Package-private access
    context.reset()  // Package-private method

    context
  }

  def resetAll(): Unit = {
    contexts.values.foreach(_.reset())  // Package-private method access
  }
}

Cross-Package Access Control

// File: src/main/scala/com/company/myapp/data/Database.scala
package com.company.myapp.data

class Database {
  // Private to the data package
  private[data] val connectionPool: String = "connection-pool"

  // Private to the myapp package (accessible from any subpackage of myapp)
  private[myapp] val systemDatabase: String = "system-db"

  // Private to the company package
  private[company] val globalConfig: String = "global-config"

  private[data] def executeInternal(query: String): String = {
    s"Executing: $query on $connectionPool"
  }

  def execute(query: String): String = {
    executeInternal(query)
  }
}

// File: src/main/scala/com/company/myapp/data/repositories/BaseRepository.scala
package com.company.myapp.data.repositories

import com.company.myapp.data.Database

abstract class BaseRepository {
  protected val db = new Database()

  // Can access package-private members from parent package
  protected def getConnectionInfo(): String = {
    db.connectionPool  // Accessible - private[data] from subpackage
  }

  // Can access myapp-level private members
  protected def getSystemDatabase(): String = {
    db.systemDatabase  // Accessible - private[myapp]
  }
}

// File: src/main/scala/com/company/myapp/services/BaseService.scala
package com.company.myapp.services

import com.company.myapp.data.Database

class BaseService {
  private val db = new Database()

  def getSystemInfo(): String = {
    // Can access myapp-level private members
    db.systemDatabase  // Accessible - private[myapp]

    // Cannot access data package private members
    // db.connectionPool  // This would not compile - private[data]
  }
}

// File: src/main/scala/com/company/otherapp/Integration.scala
package com.company.otherapp

import com.company.myapp.data.Database

class Integration {
  private val db = new Database()

  def integrate(): String = {
    // Can access company-level private members
    db.globalConfig  // Accessible - private[company]

    // Cannot access myapp or data level private members
    // db.systemDatabase  // Would not compile - private[myapp]
    // db.connectionPool  // Would not compile - private[data]
  }
}

Practical Examples

Modular Application Structure

// File: src/main/scala/com/company/ecommerce/models/package.scala
package com.company.ecommerce

package object models {
  type UserId = Long
  type ProductId = String
  type OrderId = String
  type CategoryId = String

  case class Money(amount: BigDecimal, currency: String = "USD") {
    def +(other: Money): Money = {
      require(currency == other.currency, "Currency mismatch")
      Money(amount + other.amount, currency)
    }

    def *(multiplier: Int): Money = Money(amount * multiplier, currency)

    def formatted: String = f"$amount%.2f $currency"
  }

  object OrderStatus extends Enumeration {
    type OrderStatus = Value
    val Pending, Confirmed, Shipped, Delivered, Cancelled = Value
  }
}

// File: src/main/scala/com/company/ecommerce/models/User.scala
package com.company.ecommerce.models

case class User(
  id: UserId,
  username: String,
  email: String,
  firstName: String,
  lastName: String,
  isActive: Boolean = true
) {
  def fullName: String = s"$firstName $lastName"
}

// File: src/main/scala/com/company/ecommerce/models/Product.scala
package com.company.ecommerce.models

case class Product(
  id: ProductId,
  name: String,
  description: String,
  price: Money,
  categoryId: CategoryId,
  inStock: Boolean = true
)

// File: src/main/scala/com/company/ecommerce/models/Order.scala
package com.company.ecommerce.models

import OrderStatus._

case class OrderItem(productId: ProductId, quantity: Int, unitPrice: Money) {
  def total: Money = unitPrice * quantity
}

case class Order(
  id: OrderId,
  userId: UserId,
  items: List[OrderItem],
  status: OrderStatus = Pending,
  createdAt: java.time.LocalDateTime = java.time.LocalDateTime.now()
) {
  def total: Money = items.map(_.total).reduce(_ + _)
  def itemCount: Int = items.map(_.quantity).sum
}

// File: src/main/scala/com/company/ecommerce/services/package.scala
package com.company.ecommerce

package object services {
  // Service exceptions
  case class ServiceException(message: String, cause: Option[Throwable] = None) 
    extends Exception(message, cause.orNull)

  case class ValidationException(errors: List[String]) 
    extends Exception(s"Validation failed: ${errors.mkString(", ")}")

  case class NotFoundException(resourceType: String, id: String) 
    extends Exception(s"$resourceType not found: $id")

  // Result types
  type ServiceResult[T] = Either[ServiceException, T]

  def success[T](value: T): ServiceResult[T] = Right(value)
  def failure[T](message: String): ServiceResult[T] = Left(ServiceException(message))
  def notFound[T](resourceType: String, id: String): ServiceResult[T] = 
    Left(NotFoundException(resourceType, id))
}

// File: src/main/scala/com/company/ecommerce/services/UserService.scala
package com.company.ecommerce.services

import com.company.ecommerce.models._

class UserService {
  private var users: Map[UserId, User] = Map.empty
  private var nextId: UserId = 1

  def createUser(username: String, email: String, firstName: String, lastName: String): ServiceResult[User] = {
    if (users.values.exists(_.username == username)) {
      failure("Username already exists")
    } else if (users.values.exists(_.email == email)) {
      failure("Email already exists")
    } else {
      val user = User(nextId, username, email, firstName, lastName)
      users = users + (nextId -> user)
      nextId += 1
      success(user)
    }
  }

  def findUser(id: UserId): ServiceResult[User] = {
    users.get(id) match {
      case Some(user) => success(user)
      case None => notFound("User", id.toString)
    }
  }

  def updateUser(id: UserId, updates: User => User): ServiceResult[User] = {
    users.get(id) match {
      case Some(user) =>
        val updatedUser = updates(user)
        users = users + (id -> updatedUser)
        success(updatedUser)
      case None => notFound("User", id.toString)
    }
  }
}

// File: src/main/scala/com/company/ecommerce/Application.scala
package com.company.ecommerce

// Import all models and services
import models._
import services._

// Import specific utilities
import com.company.ecommerce.models.OrderStatus._

object Application {
  def main(args: Array[String]): Unit = {
    val userService = new UserService()

    // Create users
    val user1Result = userService.createUser("alice", "alice@example.com", "Alice", "Johnson")
    val user2Result = userService.createUser("bob", "bob@example.com", "Bob", "Smith")

    (user1Result, user2Result) match {
      case (Right(alice), Right(bob)) =>
        println(s"Created users: ${alice.fullName}, ${bob.fullName}")

        // Create products
        val laptop = Product("P001", "Gaming Laptop", "High-performance laptop", Money(BigDecimal("1299.99")), "ELECTRONICS")
        val mouse = Product("P002", "Wireless Mouse", "Ergonomic wireless mouse", Money(BigDecimal("49.99")), "ELECTRONICS")

        // Create order
        val orderItems = List(
          OrderItem(laptop.id, 1, laptop.price),
          OrderItem(mouse.id, 2, mouse.price)
        )

        val order = Order(
          id = "ORD001",
          userId = alice.id,
          items = orderItems,
          status = Confirmed
        )

        println(s"Order ${order.id}: ${order.itemCount} items, total: ${order.total.formatted}")

      case (Left(error), _) => println(s"Failed to create alice: ${error.message}")
      case (_, Left(error)) => println(s"Failed to create bob: ${error.message}")
    }
  }
}

Summary

In this lesson, you've mastered packages and imports for organizing Scala code:

Package Structure: Creating logical hierarchies and nested packages
Import Management: Selective imports, aliases, and scoping
Package Objects: Shared utilities and type definitions
Visibility Control: Package-private, protected, and cross-package access
Modular Architecture: Building well-organized applications
Best Practices: Code organization patterns for maintainability

Proper package organization is essential for building scalable Scala applications that are easy to maintain and navigate.

What's Next

In the next lesson, we'll explore "Visibility and Access Control: Securing Your Code." You'll learn advanced techniques for controlling access to your classes and methods, creating clean APIs, and implementing security through proper encapsulation.

This will complete your understanding of Scala's organizational features and prepare you for building production-ready applications.

Ready to master access control? Let's continue!