Scala
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Scala
-
Section 4: Functional Scala
-
Section 5: Collections in Depth
-
Section 6: Type System
-
Section 7: Concurrency and Ecosystem
-
Section 8: Practical Projects
-
Section 9: Interview Practice
-
Section 10: Data Structures and Algorithms in Scala
-
Section 11: More Practice Exercises
-
Section 12: Advanced Functional Patterns
-
Section 13: More Ecosystem
-
Section 14: Scala Collections Library Deep Dive
-
Section 15: Scala Standard Library Deep Dive
-
Section 16: Akka Ecosystem Deep Dive
-
Section 17: Cats and Cats Effect Deep Dive
-
Section 18: Play Framework Deep Dive
-
Section 19: Apache Spark with Scala Deep Dive
-
Section 20: Scala Build Tools Deep Dive
-
Section 21: Scala 3 Specific Features
-
90: Union and Intersection Types
-
Section 22: Scala Testing Deep Dive
-
Section 23: Functional Domain Modeling
-
Section 24: More Data Structures and Algorithms in Scala
-
Section 25: Scala for Data Engineering
-
Section 26: More Practical Projects
-
Section 27: More Interview and Review
-
Section 28: ZIO Ecosystem Deep Dive
-
Section 29: Scala for Machine Learning
-
Section 30: Scala Microservices Architecture
-
Section 31: Scala Type System Deep Dive
-
Section 32: More Practice and Drills
-
Section 33: Scala Performance Deep Dive
-
Section 34: Scala Ecosystem Tooling
-
Section 35: Scala for Reactive Systems
-
Section 36: More Real-World Case Studies
-
Section 37: Scala for Financial Systems
-
Section 38: Scala GraphQL and gRPC
-
Section 39: More Final Projects
-
Section 40: More Interview and Final Review
-
Section 41: Scala for Streaming Data
-
Section 42: Scala Security Practices
-
Section 43: More Language Deep Dive
-
Section 44: Scala Command-Line Tools
-
Section 45: Scala Documentation and Style
-
Section 46: Scala Dependency Management
-
Section 47: More Practical Backend Patterns
-
Section 48: Scala for Event-Driven Architecture
-
Section 49: More Practice Drills Round 2
-
Section 50: Scala Compiler Deep Dive
-
Section 51: Scala for Web Frontends
-
Section 52: More Data Engineering Practice
-
Section 53: Scala Observability
-
Section 54: More Advanced Practice Projects
-
Section 55: Scala for Legacy Java Integration
-
Section 56: More Testing Practice
-
Section 57: Final Mastery Review
-
Section 58: Scala History and Ecosystem Context
-
Section 59: More Concurrency Patterns
-
Section 60: Scala for Configuration Management
-
Section 61: More Domain Modeling Practice
37: Building a Command-Line Scala Application
Where do the command-line arguments actually go?
When you're building a CLI tool, the entry point is everything. In modern Scala (Scala 3), the easiest way to do this is using the @main annotation. The arguments passed from the shell are injected as a List[String] (or Array[String] in older versions) into your method parameters.
Let's say we're building a tool called LogSifter—a utility that scans a log file and prints only the lines containing a specific error code. You don't want to hardcode the file path or the error code; you want to pass them in at runtime.
@main def runLogSifter(filePath: String, errorCode: String): Unit = {
println(s"Scanning $filePath for errors matching: $errorCode...")
val source = scala.io.Source.fromFile(filePath)
try {
source.getLines()
.filter(_.contains(errorCode))
.foreach(println)
} finally {
source.close()
}
}
If you run this via sbt "run /main runLogSifter server.log ERR_404", Scala maps server.log to filePath and ERR_404 to errorCode. It's clean, and it avoids the boilerplate of a main method inside an object.
How do I handle optional flags without the app crashing?
Using positional arguments like the example above is fine for simple tools, but it's brittle. If the user forgets an argument, the app crashes with an IndexOutOfBoundsException or a missing argument error. I've spent way too many hours debugging poorly written CLI tools that just crash without telling the user why.
For anything beyond a script, I recommend mapping your arguments to a case class. This separates the "parsing" logic from the "business" logic. While there are great libraries like scopt for this, you can do a lot with basic pattern matching for smaller tools.
case class Config(filePath: String, keyword: String, verbose: Boolean)
def parseArgs(args: List[String]): Config = args match {
case filePath :: keyword :: "--verbose" :: Nil => Config(filePath, keyword, true)
case filePath :: keyword :: Nil => Config(filePath, keyword, false)
case _ =>
println("Usage: logsifter <file> <keyword> [--verbose]")
sys.exit(1)
}
@main def runLogSifter(args: List[String]): Unit = {
val config = parseArgs(args)
if (config.verbose) println(s"Opening ${config.filePath}...")
// ... rest of the logic using config.keyword
}
By doing this, you've created a "safety gate." Your main logic now receives a typed Config object instead of a raw list of strings, which makes your code much easier to test.
How do I package this so someone can actually use it?
You can't expect your users to have sbt installed just to run your tool. You need to package your application into a "Fat JAR"—a single archive that contains your code and all its dependencies.
The industry standard for this in the Scala ecosystem is the sbt-assembly plugin. You add the plugin to your project/plugins.sbt file, and then you can run sbt assembly. This bundles everything into target/scala-3.x/your-app-assembly-x.x.jar.
Once you have that JAR, you can run your application from any terminal with a JVM installed using the -jar flag:
java -jar logsifter-assembly-0.1.jar server.log ERR_500 --verbose
If you want to go a step further and make it feel like a real native command (where you just type logsifter), you can create a simple bash alias or a small shell script wrapper that calls the java -jar command behind the scenes. I usually prefer the wrapper script because it allows you to hide the messy JVM flags from the end user.
📋 Practical Task
Exercise: Create the "Project-Word-Counter" CLI
Your task is to build a command-line tool that counts the occurrences of a specific word across a given text file. This will test your ability to handle arguments, perform file I/O, and implement a basic "help" menu.
- Requirement 1: The app must take two positional arguments: a
filePath(String) and atargetWord(String). - Requirement 2: The app must support an optional flag
--case-insensitive. If present, searching for "Scala" should also find "scala" or "SCALA". - Requirement 3: If the user provides the wrong number of arguments or an unknown flag, the program should print a usage message:
Usage: wordcount <file> <word> [--case-insensitive]and exit with a non-zero status code. - Requirement 4: The output should be a simple sentence:
"The word '[word]' appeared [count] times in [file]."
Test your tool with these scenarios:
runWordCount mytext.txt Scala(Case sensitive)runWordCount mytext.txt Scala --case-insensitive(Case insensitive)runWordCount mytext.txt(Should trigger usage message)
There are no comments for now.