1. Overview

While SBT is often the go-to build tool for Scala projects, nonetheless, Maven remains a compelling choice under certain circumstances — for example, in environments heavily invested in Maven, where projects may involve multiple programming languages.

Thanks to its wide-ranging plugin ecosystem, Maven offers a unified solution for complex project release management needs.

Installing Maven is straightforward. It typically involves downloading and adding it to the system’s PATH. For a more detailed walkthrough on installing Maven across different operating systems, we can refer to Baeldung’s tutorial with step-by-step instructions for getting Maven up and running on our machines for all the major operating systems.

In the rest of the tutorial, we’ll assume we already have a working copy of Maven and focus on exploring how to use it to build, test, and package a Scala application.

2. Hello Maven

We’ll use modules since that would be the most common type of project combining Scala and Maven. Therefore, we’ll start with a folder as the root of our project. In the root folder**,** we’ll have the main pom.xml file that holds the common configuration for our modules.

We’ll start with the universally recognized “Hello, World!” program. This way, we can focus on the structure of our project and the Maven configuration for it.

2.1. Everyone Needs a Parent

Let’s start with a minimal parent POM that adds our initial hello-world submodule:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.baeldung</groupId>
  <artifactId>scala-with-maven</artifactId>
  <version>${revision}</version>
  <packaging>pom</packaging>

  <properties>
    <revision>1.0-SNAPSHOT</revision>
    <scala.version>3.4.0</scala.version>
  </properties>

  <modules>
    <module>hello-world</module>
  </modules>
</project>

In the parent POM file, we declare some properties to manage the version of our project and the version of Scala. This will allow us to update them in a single place for all the submodules. In addition, we include the submodule in the modules section.

Please visit our detailed tutorial on multi-module Maven projects for more information.

2.2. Hello World

Next, we’ll add a minimal POM to the module, using the Scala Compiler plugin to build our Scala code:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.baeldung</groupId>
    <artifactId>scala-with-maven</artifactId>
    <version>${revision}</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <artifactId>hello-world</artifactId>

  <dependencies>
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala3-library_3</artifactId>
      <version>${scala.version}</version>
    </dependency>
  </dependencies>

  <build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <plugins>
      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>scala-maven-plugin</artifactId>
        <version>4.8.1</version>
        <configuration>
          <recompileMode>incremental</recompileMode>
          <scalaVersion>${scala.version}</scalaVersion>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Here, we add the Scala Compiler Plugin and tell Maven where to find our Scala code using the sourceDirectory element at the start of the build declaration.

We manage dependencies in the same fashion we would in a Java program. Finally, we control the Scala version used to compile our program by adding the Scala version element in the configuration section.

Finally, we create the source folder src/main/scala where we’ll store our simple Scala application:

package com.baeldung.scalawithmaven

object HelloWorld {

  def message: String = "Hello, World!"

  def main(args: Array[String]): Unit = println(message)
}

When running mvn compile, our code will be compiled, and the classes will be saved to the target/classes subfolder.**

3. Adding Tests

Tests are critical for any software project that aspires to be of good quality. Maven wouldn’t be a viable tool for serious Scala projects without support for running Scala tests.

In this section, we’ll discuss how to add Scala Tests to our Maven Project. The process involves adding a plug-in to the build and configuring it to run our tests.

First, let’s start by creating a folder (src/tests/scala) for our tests and adding a simple Scala Test to it:

package com.baeldung.scalawithmaven

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class HelloWorldSpec extends AnyFlatSpec with Matchers {
  
  "The HelloWorld object" should "say hello" in {
    val greeting = HelloWorld.message
    greeting shouldEqual "Hello, World!"
  }
}

At this point, Maven is still ignoring our tests. Hence, we have to configure it to compile and run them. To that effect, we need to add the folder to the build using the testSourceDirectory element and the scalatest-maven-plugin in our module’s pom.xml file:

... 
  <dependencies>
    ...
    <dependency>
      <groupId>org.scalatest</groupId>
      <artifactId>scalatest_3</artifactId>
      <version>3.2.18</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    ...
    <testSourceDirectory>src/tests/scala</testSourceDirectory>
    <plugins>
      ...
      <plugin>
        <groupId>org.scalatest</groupId>
        <artifactId>scalatest-maven-plugin</artifactId>
        <version>2.2.0</version>
        <executions>
          <execution>
            <goals>
              <goal>test</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Now, we can run the tests from the command line or supporting tool by executing mvn test.

4. Adding a Second Module

To add a second module, we create a new folder structure within our project, mirroring the hello-world module for consistency. This new module, named hello-rest, has its own Scala source files and tests, placed within src/main/scala and src/test/scala, respectively.

Then, we add the code for a minimal REST Service that responds with “Hello, REST” on the hello endpoint:

import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import cats.effect._

val helloWorldService = HttpRoutes.of[IO] {
  case GET -> Root / "hello" => Ok("Hello, REST!")
}

After adding the code, we need to copy the pom.xml file from the hello-world module, then change the artifactId to hello-rest, remove the unnecessary sections, and add the HTTP dependencies to it:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.baeldung</groupId>
    <artifactId>scala-with-maven</artifactId>
    <version>${revision}</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <artifactId>hello-rest</artifactId>

  <dependencies>
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala3-library_3</artifactId>
      <version>${scala.version}</version>
    </dependency>
    <dependency>
      <groupId>org.typelevel</groupId>
      <artifactId>cats-effect_3</artifactId>
      <version>3.5.4</version>
    </dependency>
    <dependency>
      <groupId>org.http4s</groupId>
      <artifactId>http4s-blaze-server_3</artifactId>
      <version>1.0.0-M39</version>
    </dependency>
    <dependency>
      <groupId>org.http4s</groupId>
      <artifactId>http4s-dsl_3</artifactId>
      <version>1.0.0-M41</version> 
    </dependency>
  </dependencies>

  <build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <testSourceDirectory>src/tests/scala</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>scala-maven-plugin</artifactId>
        <version>4.8.1</version>
        <configuration>
          <recompileMode>incremental</recompileMode>
          <scalaVersion>${scala.version}</scalaVersion>
          <javacArgs>
            <javacArg>-Xlint:unchecked</javacArg>
            <javacArg>-Xlint:deprecation</javacArg>
          </javacArgs>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

To complete the process, we need to add the new module to the parent pom.xml file, after which both modules will be compiled and tested when executing the corresponding Maven goals.

5. Keeping Our POMs Dry

With the above steps, we complete the addition and have a fully functional Maven project, but there’s some repetition in the POM files that could become hard to maintain if the number of modules increases.

To minimize this repetition, we can use the dependencyManagement and pluginManagement elements in the parent POM. We still need to add the dependencies in each module’s POMs, but we won’t have to mention the versions, allowing us to control the versions centrally.

Finally, let’s add the plugins used in all the modules to the build section in the parent POM, saving us from having to add the plugins individually to each module:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.baeldung</groupId>
  <artifactId>scala-with-maven</artifactId>
  <version>${revision}</version>
  <packaging>pom</packaging>

  <properties>
    <revision>1.0-SNAPSHOT</revision>
    <scala.version>3.4.0</scala.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala3-library_3</artifactId>
        <version>${scala.version}</version>
      </dependency>
      <dependency>
        <groupId>org.scalatest</groupId>
        <artifactId>scalatest_3</artifactId>
        <version>3.2.18</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
  
  <modules>
    <module>hello-world</module>
    <module>hello-rest</module>
  </modules>

  <build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <testSourceDirectory>src/test/scala</testSourceDirectory>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.scalatest</groupId>
          <artifactId>scalatest-maven-plugin</artifactId>
          <version>2.2.0</version>
          <executions>
            <execution>
              <goals>
                <goal>test</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>scala-maven-plugin</artifactId>
        <version>4.8.1</version>
        <configuration>
          <recompileMode>incremental</recompileMode>
          <scalaVersion>${scala.version}</scalaVersion>
          <javacArgs>
            <javacArg>-Xlint:unchecked</javacArg>
            <javacArg>-Xlint:deprecation</javacArg>
          </javacArgs>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

In this configuration, we assume that all modules have Scala code, but not all have Scala tests. Hence, the compiler plugin is in the plugin section in the parent POM, but the scalatest plugin is in the pluginManagement section.

As a result, the hello-rest module only needs a dependencies section but no build section:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.baeldung</groupId>
    <artifactId>scala-with-maven</artifactId>
    <version>${revision}</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <artifactId>hello-rest</artifactId>

  <dependencies>
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala3-library_3</artifactId>
    </dependency>
    <dependency>
      <groupId>org.typelevel</groupId>
      <artifactId>cats-effect_3</artifactId>
      <version>3.5.4</version>
    </dependency>
    <dependency>
      <groupId>org.http4s</groupId>
      <artifactId>http4s-blaze-server_3</artifactId>
      <version>1.0.0-M39</version>
    </dependency>
    <dependency>
      <groupId>org.http4s</groupId>
      <artifactId>http4s-dsl_3</artifactId>
      <version>1.0.0-M41</version> 
    </dependency>
  </dependencies>

</project>

But the hello-world module needs a build section with the scalatest plugin:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.baeldung</groupId>
    <artifactId>scala-with-maven</artifactId>
    <version>${revision}</version>
    <relativePath>../pom.xml</relativePath>
  </parent>

  <artifactId>hello-world</artifactId>
  
  <dependencies>
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala3-library_3</artifactId>
      <version>${scala.version}</version>
    </dependency>
    <dependency>
      <groupId>org.scalatest</groupId>
      <artifactId>scalatest_3</artifactId>
      <version>3.2.18</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.scalatest</groupId>
        <artifactId>scalatest-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

6. Conclusion

In this tutorial, we learned how to use Maven to manage a Scala project and which Maven plugins to use to compile and test Scala code.

Then, we discovered how to use the Maven dependency management configuration features to minimize repetition in our POM files and centralize the handling of dependency versions. Finally, we moved the plugins to the parent pom.xml, allowing us to avoid adding them to each module’s POM file.

As always, the complete source code for the examples is available over on GitHub.