Scala is an excellent choice for building highly scalable, low-latency, multithreaded applications. It has strong typing, a concise syntax, and a highly functional design. It's especially good for building long-running services, and at solving problems with strongly-defined data structures and a lot of concurrency. With the addition of tooling like Akka or ZIO, it also becomes an especially good choice for streaming applications, event-driven applications, and more.
There are plenty of times when Scala is not the right tool for the job, however, and in this article we'll discuss some of the best use cases for Scala, along with some poor ones. We'll also look at some of the dangers of "language purity" and how picking the right tool for the job, and adding more skills to your skillset, can make a more effective developer.
So let's take a look at some of the strengths and weaknesses of Scala. These are not exhaustive, but stand out as some of the most fundamental aspects of the language.
Strong compile-time guarantees: Scala is statically typed, functional by design, and has a compiler which does an excellent job of catching problems early on. It has a good balance of concise syntax and strong compile-time guarantees, provided you write idiomatic Scala.
Excellent concurrency handling: Scala is fast and multi-threaded, and uses functional paradigms to deal with concurrency safely in a non-blocking manner and to avoid race conditions. Out of the box the Futures API helps handle non-blocking concurrency, and the language is geared towards functional handling of data, e.g. using case classes which are copied instead of modified in place, preventing races to update data in memory. Popular libraries provide even more advanced concurrency management.
Runs on the JVM: the JVM is a very stable virtual machine, with garbage collection and various other features (too many to tackle here) provided out of the box. You also have guaranteed long-term support.
Fast: while not quite the blistering speed you can achieve with low-level languages like C or Go, and trading some runtime optimisation for the guarantees of a functional design, Scala runs fast. It's a great deal faster than an interpreted language like Python or Javascript, and has comparable performance to Java, C#, Go, or others. In most applications, speed is unlikely to be the bottleneck.
Java interoperability: There are a great many Java libraries available and you can use them freely in Scala even if a Scala version doesn't exist. This also means if you have in-house Java you can make use of any internal Java libraries you've already developed.
Steep learning curve: Scala is a difficult language to learn, and even more difficult to master. Experienced scala developers are expensive and hard to find, and training new scala developers can be time consuming.
Minimum overhead for a project: the smallest possible project will typically still require a
build.sbt
, some plugins like sbt-assembly
, and some dependencies and configuration such as logging.
To run the application you'll need to use something like java -jar myapp.jar [arguments]
. An
interpreted language like python, javascript, bash, etc. could be a single file with no build process and
a simpler cli invocation.
Slower to develop: compilation, static types, and functional design are all features which give you greater confidence in the reliability of your application, but the trade-off is that a simpler, dynamic, interpreted language like python or javascript is usually faster to write, particularly for small applications or prototyping.
Runs on the JVM: the JVM has a certain unavoidable start-up time and memory usage cost. This makes it a poor choice for CLI tools where you might need to invoke the application quickly and/or frequently. It may also require you to carefully consider how you manage the JVM in a microservices architecture.
Many attempts have been made to partially mitigate some of these negatives of Scala, with varying degrees of
success. Giter8 templates can be used to quickstart a project, or an IDE can do some of the
lifting for you. Ammonite attempts to allow simple scripting in Scala to be more viable
by allowing single-file .sc
scripts to be run; under the hood, it builds and caches the result, including
an additional syntax for declaring dependencies inline. What it does even better, is provide an excellent
REPL interface for experimenting with Scala. While I would avoid using Scala as a scripting language, I do
recommend the Ammonite REPL for quick experimentation. GraalVM is an alternative to the JVM
which has much faster start up time. ScalaJS compiles Scala into javascript, making it
possible to write Scala which will run in a web browser. Each of these solutions have their own downsides,
however, and can't make Scala the equal of languages or tooling better suited to these kinds of problems.
With the pros and cons in mind, let's consider some factors which might inform whether Scala is the right choice for your application.
Scala really shines in its safe and fast handling of concurrency using functional principles. If you're writing a low-latency, highly concurrent application, Scala is an excellent choice. For basic application designs, you can use the Futures API to handle concurrency. For larger or more complex designs, you can consider whether your application would fit well with an actor system model and consider Akka, or whether data streaming fits your use case and consider Akka stream or ZIO Streaming designs. These libraries can make designing a concurrent application much easier from a top level down, as I discussed recently in a case study of using Akka actors.
If you don't need high concurrency, you may not be making full use of the advantages of Scala, and considering other tooling may be worthwhile. This may also mean that the most complex or highly concurrent pieces of your architecture should be written in Scala, particularly with tooling such as Akka, but other components could benefit from the advantages of a more basic design in another language. For example, it's common to see Scala backend services supported by a Python or NodeJS middle-tier layer in web applications.
Scala is great at modelling data by providing strong types and case classes to fit the shape of your data. This gives you a lot of compile time guarantees about how you're using the data, and is great if you know the shape of the data at compile time. This doesn't mean the data has to have a small number of fixed shapes, though; Scala is also good at modelling optional and multi-shaped data and other complexities.
If your data is highly variable, specifically if you only know the schema to which it conforms at runtime, Scala may not be the best choice. While it's always possible to interpret your schema at runtime using libraries, if this is the primary purpose of your application, a dynamically-typed language is likely a better fit.
While Scala is appropriate for almost any size of project, it's worth considering the minimum start up cost in terms of initial setup for development and the speed at which development can take place. As Scala aims to provide strong compile-time guarantees and a large degree of runtime safety, it means a well-written and well-tested project can scale up to any size with little to no loss of confidence. The trade off for that confidence is that it typically takes longer to develop than a similar application in dynamic language.
Similarly, Scala has a lot of useful tools, but many of them aren't part of the standard library and require dependencies, and decisions to be made early on – for example, the large number of possible JSON libraries in Scala, and the range of different toolkits you might consider using.
So, consider where your application lies on the "build it fast" vs "guaranteed uptime" curve when choosing whether Scala fits. To quickly create prototypes or proof of concepts, you might instead reach for a dynamic, language like python. It may later be appropriate to take the slower but more solid approach of replacing it with a Scala service – with better concurrency handling, better scaling, and better type safety.
I'm including this section as a focus on day-to-day DevOps / development work, as I've found it common for Scala developers especially to have a certain tunnel vision or language purity with Scala. I've seen this result in developers either attempting to automate development tasks using Scala and doing so slowly and/or poorly, or giving up and doing a menial task repeatedly because they can't figure out how to elegantly automate the task in Scala. Generally, this degree language of language purity is counter-productive.
I'm a strong advocate of learning new skills and using the right tool for the job. While my specialty is Scala, it's important to be able to use one or two other languages to cover the gaps in which Scala does a poor job, especially since scripting simple tasks to speed up development is something any developer needs, and something Scala does poorly.
For me, that means having a good grasp of Bash and Python alongside my expertise with Scala. Bash is often
feared as being old, clunky and a bit arcane, but at least a grasp of using the terminal is vital for any
developer. Being able to take that one step further and automate simple tasks, especially related to working
with files or making simple HTTP calls with curl
, can make your life easier even without requiring more
than basic understanding of Bash. For slightly more complex scripts, perhaps working with pieces of JSON,
making more complex HTTP calls, or using one of many libraries, good grasp of a scripting language is also
important. I choose python due to its simple and concise syntax and
famously broad and easy toolkit, but Perl, Ruby, JavaScript, and others can fill the same
role according to personal preference.
To provide some extreme examples, this once led to me rewriting someone's 700-line overly complicated Scala
"script" which had been incorporated into a unit test file but prevented from running as a test, instead
requiring you to uncomment some lines of code, replace URL and data payload variables, and run from a
specific line from inside an IDE, with the result of writing some arbitrary data over TCP to a backend
service. Using python, this became a 15-line script which could accept an arbitrary JSON payload and command
line parameters like write-payload --host example.com -f /tmp/data.json
and be used across several
different projects.
Similarly, a standard part of the testing procedure in another project involved using a GUI application to manually perform a series of 10 steps, taking approximately 10 minutes multiple times a day, because it was non-trivial to automate. This process was being followed by half a dozen developers and QAs. A couple of hours of digging into the process and writing some python, and the whole team could eliminate that process entirely going forward.
In short: a good developer needs some working knowledge of DevOps, and at least some proficiency in scripting to perform such tasks. To avoid beating a dead horse, I leave you now with my public collection of useful scripts as inspiration for some of the types of jobs I've found worth automating in the past.
In summary: