Using Scala, SBT and IntelliJ IDEA

I really like Scala. I also really like IntelliJ IDEA. Making them play nice can sometimes be a little difficult. Here are some recommendations based on my own anecdotal experience:

  • Keep IntelliJ IDEA and the Scala plugin up-to-date
  • Increase IntelliJ IDEA heap size
  • Bound implicit parameters search depth
  • Use SBT for compilation and tests
  • Use remote debugging with SBT

Keep IntelliJ IDEA and the Scala plugin up-to-date

The IntelliJ IDEA update process definitely leads something to be desired on my Mac. I’m not sure what it looks like on other platforms (e.g. Windows or Linux), but on Mac I frequently find myself:

  • Clicking IntelliJ IDEA then Check for Updates then Download
  • Downloading a new DMG from JetBrains’ website
  • Quitting IntelliJ IDEA
  • Copying the new IntelliJ IDEA from the DMG to Applications
  • Opening IntelliJ IDEA
  • Updating the Scala plugin
  • Restarting IntelliJ IDEA
  • Potentially invalidating caches and restarting

Anyone who works on multiple projects at once (or even just one project) will know that the above is a daunting prospect. That said, I do recommend doing it on a weekly (or biweekly) basis. When I see unexpected behaviour this is also my first troubleshooting step. Oh and it certainly beats removing your ivy cache in terms of recovery time.

Check for updates

Increase IntelliJ IDEA heap size

The default IntelliJ IDEA JVM heap size on my Mac seems to be set to 512MB. If you have big projects then this is probably too small. What tends to happen for me at 512MB is that IntelliJ IDEA runs out of heap space and then gets stuck in garbage collection hell. Specifically, IntelliJ IDEA becomes laggy and / or unresponsive when typing, slow to autocomplete and to navigate around because all the CPU time is spent on never-ending garbage collection.

On Mac changing the JVM heap size used to involve leaving a brown paper bag behind a box outside the door to Jet Brains’ office and waiting for someone from Jet Brains support to notice, but its a little easier now. Click Help then Edit Custom VM Options. This will create and / or open a file in ~/Library/Preferences/<IntelliJ IDEA Version>/idea.vmpoptions. You can then change -Xmx512m to something larger for example -Xmx4096m. The official documentation links out to a Stack Overflow answer, so if you’re skim reading or your Google Fu let you down then you may find yourself exchanging brown paper bags with your colleagues (but hopefully not Jet Brains support).

Edit Custom VM Options

Bound implicit parameters search depth

The two things I like most about Scala are for-comprehensions and implicits (in that order). For-comprehensions do not really have any impact on compiler performance. Sure they are expanded to map and flatMap calls during compile time, but that feels like a straight forward find and replace (and linear time one would hope). Implicits, however, do have a performance implication at compile time. Specifically the (poorly documented) resolution of them can be extremely computationly expensive — in fact the Scala compiler even attempts to heuristically spot infinite recursion. The performance impact can be large if you make use of any libraries that use implicits to perform auto-derivation of type classes (and you really should use these libraries). The IntelliJ IDEA Scala plugin seems to default to an infinite recursion, but I’m not sure it has the same heuristic approach as the Scala compiler. To play it safe I normally bound the search to some large number e.g. 99 as opposed to the infinite default. The setting can be found by clicking IntelliJ IDEA then Preferences then Languages & Frameworks then Scala then Performance.

Bound implicit parameters search depth

Use SBT for compilation and tests

This is really a matter of personal preference, but I like to run SBT either in a separate terminal window or in the Terminal window provided by IntelliJ IDEA. My reasoning is that sometimes IntelliJ IDEA will give false negatives and SBT will not. You can have sbt automatically re-compile you code when it changes by prefixing your SBT command with a tilde (~). You can for example run ~compile and ~test to continually compile or test your code as you work.

Use SBT for compilation

Use Remote Debugging with SBT

In most projects there are going to be some complexities involved in running your application. For example, you may have a Docker compose file that contains services you depend on and you may need to pass environment variables to the application. This is easily done via the terminal and commands can be shared between you and your colleagues easily. The same cannot really be said for IntelliJ IDEA configurations.

SBT provides a convenience command line argument to enable remote debugging: sbt -jvm-debug 5005. The port is arbitrary, but you need to make sure it is the same in sbt and IntelliJ IDEA (I choose IntelliJ IDEA’s default). You’ll know if sbt started with remote debugging enabled because it will output: Listening for transport dt_socket at address: 5005. Once you have sbt up and listening you can connect to it from IntelliJ IDEA, but first you have to create a Remote debug configuration (you may have one already). To do this you click Run then Edit Configurations then + then Remote. The defaults should be fine (double check the port though) and all you need to do is give the configuration a sensible name (I choose SBT).

Run/Debug Configurations

Once you are happy with the configuration click OK. You can now click Run -> Debug ‘SBT’. IntelliJ will connect to SBT on port 5005 and any break points you set will be respected. This is great for understanding failing tests or trying to understand or recreate issues that you have seen in production. One small thing to watch out for is projects where SBT has been configured to fork the JVM (fork := true). You’ll need to disable that if you want it to work or to configure SBT to pass the parameters in the IntelliJ IDEA configuration using javaOptions.

Debugging a project