Technology  /  Scala

🔴 Scala 21 guides · updated 2026

The JVM's hybrid OOP/functional language — immutability, pattern matching, and the type system that powers Spark and Akka, from first principles to production.

How Scala Actually Works: The JVM, sbt, and the Hybrid OOP-FP Language

Most Scala tutorials open with val x = 5 and a claim that Scala is “a better Java” or “a functional language on the JVM,” then move straight into syntax without ever explaining what either of those claims actually means in practice. That gap is exactly why so many developers bounce off Scala early — they learn the syntax without the mental model that makes the syntax make sense.

This guide fills that gap: what actually happens when Scala code runs, why the language deliberately mixes object-oriented and functional programming instead of picking one, and what that mixture buys you that neither paradigm gets you alone.


Scala Runs on the JVM — And That Decision Explains a Lot

Scala was created by Martin Odersky, released in 2004, with a deliberate design goal: bring the expressiveness of functional languages like ML and Haskell to a platform with real industrial adoption — the Java Virtual Machine — rather than asking developers to abandon the JVM’s mature tooling, libraries, and deployment story to get functional programming benefits.

scalac compiler

javac compiler

Scala source (.scala)

JVM bytecode (.class files)

Java source (.java)

Same JVM runtime

executes both, interchangeably

This single decision explains most of what makes Scala practical rather than academic: a Scala class can extend a Java class, a Scala method can call a Java library directly with zero adapter code, and a Scala .jar deploys anywhere a Java .jar does — the same application servers, the same containers, the same monitoring tools. Compare this to genuinely separate functional languages like Haskell or OCaml, which require their own runtime and their own interop story to talk to Java code at all. Scala’s interop is native because the compiled output is, at the bytecode level, indistinguishable from Java’s.

This is also why Scala became the dominant language for Apache Spark, Kafka’s ecosystem tooling, and Akka — all JVM-native, all needing to interoperate freely with the existing Java ecosystem those systems were built alongside.


The Hybrid Design: Why Not Just Pick One Paradigm?

Scala’s name is a portmanteau of “scalable language” — the idea being that the same language should scale from small scripts to large distributed systems, and that scaling requires both object-oriented structure (for organizing large codebases into cohesive units) and functional programming (for reasoning correctly about state and concurrency, which becomes exponentially harder in purely imperative, mutable code as a system grows).

// Everything in Scala is an object — even functions
val greet: String => String = name => s"Hello, $name!"
println(greet("Alice"))
// Classes organize behavior, the OOP half
class Greeter(prefix: String) {
def greet(name: String): String = s"$prefix, $name!"
}
val greeter = new Greeter("Hey")
println(greeter.greet("Bob"))

Both snippets above are equally idiomatic Scala. The first treats a function as a first-class value that can be passed around, stored, and composed — pure functional style. The second uses classes and methods to organize state and behavior — familiar OOP style. Neither is “more correct” — Scala’s actual position is that different problems call for different tools, and forcing everything into one paradigm (as strict object-oriented languages force everything into classes, or as pure functional languages forbid mutable state entirely) creates friction rather than clarity for real, large systems.

What “everything is an object” actually means

1 + 2 // this is actually 1.+(2) — a method call on an Int object
def double(x: Int) = x * 2 // functions are objects too, instances of Function1[Int, Int]

Unlike Java, where primitives (int, boolean) are genuinely distinct from objects, Scala treats every value — including numbers and functions themselves — as an object with methods. The JVM’s actual runtime representation still uses primitive int under the hood for performance (Scala’s compiler optimizes this away), but at the language level, there’s no special case: 1 + 2 is syntactic sugar for calling the + method on the object 1, passed the argument 2. This uniformity is what lets functions be passed around exactly like any other value — a function is simply an object implementing a apply method, nothing more exotic than that.


Compiling and Running Your First Scala Program

Hello.scala
@main def hello(): Unit =
println("Hello, Scala")
Terminal window
scala Hello.scala # Scala 3's scala-cli-style runner: compiles and runs in one step
// The more traditional, explicit form — still fully supported and common in larger projects
object Hello {
def main(args: Array[String]): Unit = {
println("Hello, Scala")
}
}
Terminal window
scalac Hello.scala # compiles to Hello.class (JVM bytecode)
scala Hello # runs the compiled bytecode on the JVM

The @main annotation (Scala 3) is a newer, less ceremonious entry point than the Java-style object with a main method — both compile to equivalent bytecode, and both remain valid; large existing codebases (and any code targeting Scala 2 compatibility) still favor the explicit object form, so recognizing both matters more than picking a permanent favorite.


The REPL — Scala’s Fastest Feedback Loop

Terminal window
scala
scala> val x = 10
val x: Int = 10
scala> x * 2
val res0: Int = 20
scala> def square(n: Int) = n * n
def square(n: Int): Int
scala> square(5)
val res1: Int = 25

The REPL (Read-Eval-Print Loop) evaluates expressions immediately and prints both the inferred type and the result — this immediate type feedback is genuinely useful while learning, since it makes Scala’s type inference visible rather than abstract. Experienced Scala developers use the REPL constantly for testing small expressions, checking a library API’s exact behavior, or verifying a type signature before committing it to a file — far more casually than most Java developers use jshell, partly because Scala’s expression-oriented syntax (covered in the next guide) makes small REPL experiments read naturally.


sbt and Scala CLI — Two Different Tools for Two Different Jobs

Quick script or single-file experiment

scala-cli / scala command

(fast, minimal setup, no project structure needed)

Real multi-file project

with dependencies, tests, packaging

sbt

(the standard, if slower, build tool)

sbt (Scala Build Tool) is the long-standing standard for real projects — it manages dependencies, compiles incrementally, runs tests, and packages artifacts, roughly analogous to Maven or Gradle in the Java world, but configured in Scala itself rather than XML. It has a reputation (partly deserved) for a slow first-run JVM startup and a build DSL with a learning curve of its own. scala-cli (bundled into the scala command since Scala 3.x tooling matured) exists specifically to remove that friction for smaller cases — running a single file, trying out a library, or writing a quick script — without needing a build.sbt file or a project directory structure at all. The next guide in this series sets up a real sbt project in depth.


Static Typing With Aggressive Inference

val name = "Alice" // inferred as String — no annotation needed
val age: Int = 30 // explicit annotation — optional here, sometimes clearer
val numbers = List(1, 2, 3) // inferred as List[Int]
def add(a: Int, b: Int): Int = a + b // parameters must be annotated; return type usually inferable

Scala is statically typed — like Java, type errors are caught at compile time, not discovered at runtime. Unlike Java, Scala’s type inference is dramatically more aggressive: local variable types are essentially never written out explicitly in idiomatic code, because the compiler can determine them from the assigned value with high reliability. This gives Scala code a visual density closer to a dynamically-typed language like Python, while retaining every one of static typing’s safety guarantees — the types are still there and still checked, they’re just not visually present in the source unless you choose to write them for clarity.


Why Companies Actually Choose Scala

Big data and streaming systems. Apache Spark’s core is written in Scala, and while Spark supports Python and Java APIs, the Scala API is generally the most complete and the first to receive new features — a genuine practical reason for teams doing heavy Spark work to know Scala even if their day-to-day application code is in Python.

Concurrent and distributed systems. Akka (actor-model concurrency) and its descendants power a large share of high-throughput, low-latency financial and telecom systems — Scala’s combination of JVM performance, strong typing, and functional patterns for managing state maps well onto systems that need to handle enormous message volumes correctly.

Teams wanting functional programming with JVM interop. Companies with existing JVM infrastructure (deployment pipelines, monitoring, internal libraries) who want functional programming’s correctness benefits without abandoning that infrastructure land on Scala specifically because of the seamless Java interop covered above.

Where Scala is a worse fit: small scripts and quick automation (Python’s ecosystem and lower ceremony usually win), teams with no JVM operational experience at all (the learning curve compounds if you’re learning the JVM and Scala simultaneously), and greenfield projects with no big-data or high-concurrency requirement, where a simpler language may reduce onboarding friction for a team that doesn’t need Scala’s specific strengths.


Frequently Asked Questions

Is Scala just “a nicer Java”? Not quite — while full Java interop is real and valuable, Scala’s actual design goals (functional programming as a first-class citizen, pattern matching, immutability by default, powerful type inference) push toward genuinely different code structure than idiomatic Java, not just cleaner syntax over the same patterns.

Do I need to learn Java first? No — Scala is a complete, self-sufficient language. Java knowledge helps with understanding the JVM ecosystem (build tools, deployment, common libraries) but isn’t a prerequisite for the language itself.

Is Scala 2 or Scala 3 what I should learn? Scala 3 (also called Dotty during its development) is the current, actively developed version, with a cleaner syntax and several improvements covered later in this series. Scala 2 remains extremely common in existing production codebases and much of the Spark/Akka ecosystem historically ran on it, so recognizing Scala 2 syntax (especially around implicits, covered in its own guide) still matters for working in real codebases.

Why does Scala have a reputation for being hard to learn? Largely because it exposes powerful features (implicits, advanced type system constructs, functional composition patterns) that Java never asked developers to reason about — the learning curve is real, but it’s a curve toward genuine expressive power, not accidental complexity, and this series is structured to build toward those advanced features gradually rather than front-loading them.

Is Scala’s functional programming the same as Haskell’s? No — Scala supports functional programming as an option layered onto an object-oriented foundation, with mutable state and side effects still fully permitted when appropriate. Haskell enforces purity at the language level (no mutation, no side effects without an explicit type-level marker). Scala’s approach is pragmatic: use immutability and pure functions where they help, use OOP and mutation where they’re clearer — a deliberate design trade-off, not an oversight.

Does Scala compile slower than Java? Generally yes, noticeably so for larger codebases — the price of the more powerful type inference and implicit resolution covered later in this series. Incremental compilation (which both sbt and modern IDE tooling support) keeps this manageable day-to-day, but a clean full build of a large Scala project is a genuinely slower step than the Java equivalent.


What’s Next

You now understand why Scala looks the way it does: JVM-native for real interop, statically typed with aggressive inference for both safety and readability, and deliberately hybrid between OOP and functional programming rather than picking one side. Next: Setting Up a Scala Project, where you’ll configure a real sbt build, understand build.sbt, and get a multi-file project running end to end.