Java Web Build Examples

This page contains examples of using Mill as a build tool for web-applications. It covers setting up a basic backend server with a variety of server frameworks

Jetty Hello World App

build.sc (download, browse)
import mill._, javalib._

object hello extends RootModule with JavaModule {
  def ivyDeps = Agg(
    ivy"org.eclipse.jetty:jetty-server:9.4.43.v20210629",
    ivy"javax.servlet:javax.servlet-api:4.0.1"
  )

  object test extends JavaTests with TestModule.Junit4
}

This example demonstrates how to set up a simple Jetty webserver, able to handle a single HTTP request at / and reply with a single response.

> mill test
...HelloJettyTest.testHelloJetty finished...

> mill runBackground

> curl http://localhost:8085
...<h1>Hello, World!</h1>...

> mill clean runBackground

Spring Boot Hello World App

build.sc (download, browse)
import mill._, javalib._

object hello extends RootModule with JavaModule {
  def ivyDeps = Agg(
    ivy"org.springframework.boot:spring-boot-starter-web:2.5.6",
    ivy"org.springframework.boot:spring-boot-starter-actuator:2.5.6"
  )

  object test extends JavaTests with TestModule.Junit5 {
    def ivyDeps = super.ivyDeps() ++ Agg(
      ivy"org.springframework.boot:spring-boot-starter-test:2.5.6"
    )
  }
}

This example demonstrates how to set up a simple Spring Boot webserver, able to handle a single HTTP request at / and reply with a single response.

> mill test
...com.example.HelloSpringBootTest#shouldReturnDefaultMessage() finished...

> mill runBackground

> curl http://localhost:8086
...<h1>Hello, World!</h1>...

> mill clean runBackground

Spring Boot TodoMvc App

build.sc (download, browse)
import mill._, javalib._

object hello extends RootModule with JavaModule {
  def ivyDeps = Agg(
    ivy"org.springframework.boot:spring-boot-starter-data-jpa:2.5.4",
    ivy"org.springframework.boot:spring-boot-starter-thymeleaf:2.5.4",
    ivy"org.springframework.boot:spring-boot-starter-validation:2.5.4",
    ivy"org.springframework.boot:spring-boot-starter-web:2.5.4",

    ivy"javax.xml.bind:jaxb-api:2.3.1",

    ivy"org.webjars:webjars-locator:0.41",
    ivy"org.webjars.npm:todomvc-common:1.0.5",
    ivy"org.webjars.npm:todomvc-app-css:2.4.1",


  )

  trait HelloTests extends JavaTests with TestModule.Junit5{
    def mainClass = Some("com.example.TodomvcApplication")
    def ivyDeps = super.ivyDeps() ++ Agg(
      ivy"org.springframework.boot:spring-boot-starter-test:2.5.6"
    )
  }

  object test extends HelloTests{
    def ivyDeps = super.ivyDeps() ++ Agg(
      ivy"com.h2database:h2:2.3.230",
    )
  }

  object integration extends HelloTests {
    def ivyDeps = super.ivyDeps() ++ Agg(
      ivy"org.testcontainers:testcontainers:1.18.0",
      ivy"org.testcontainers:junit-jupiter:1.18.0",
      ivy"org.testcontainers:postgresql:1.18.0",
      ivy"org.postgresql:postgresql:42.6.0",
    )
  }
}

This is a larger example using Spring Boot, implementing the well known TodoMVC example app. Apart from running a webserver, this example also demonstrates:

  • Serving HTML templates using Thymeleaf

  • Serving static Javascript and CSS using Webjars

  • Querying a SQL database using JPA and H2

  • Unit testing using a H2 in-memory database

  • Integration testing using Testcontainers Postgres in Docker

> mill test
...com.example.TodomvcTests#homePageLoads() finished...
...com.example.TodomvcTests#addNewTodoItem() finished...

> mill integration
...com.example.TodomvcIntegrationTests#homePageLoads() finished...
...com.example.TodomvcIntegrationTests#addNewTodoItem() finished...

> mill test.runBackground

> curl http://localhost:8087
...<h1>todos</h1>...

> mill clean runBackground

Micronaut Hello World App

build.sc (download, browse)
import mill._, javalib._

object hello extends RootModule with MicronautModule {
  def micronautVersion = "4.5.3"
  def ivyDeps = Agg(
    ivy"io.micronaut:micronaut-http-server-netty:$micronautVersion",
    ivy"io.micronaut.serde:micronaut-serde-jackson:2.10.1",
    ivy"ch.qos.logback:logback-classic:1.5.3",
  )


  object test extends MavenTests with TestModule.Junit5{
    def ivyDeps = super.ivyDeps() ++ Agg(
      ivy"io.micronaut:micronaut-http-client:$micronautVersion",
      ivy"io.micronaut.test:micronaut-test-junit5:4.4.0",
      ivy"org.junit.jupiter:junit-jupiter-api:5.8.1",
      ivy"org.junit.jupiter:junit-jupiter-engine:5.8.1"
    )
  }
}

trait MicronautModule extends MavenModule{
  def micronautVersion: String

  def processors = T{
    defaultResolver().resolveDeps(
      Agg(
        ivy"io.micronaut.data:micronaut-data-processor:4.7.0",
        ivy"io.micronaut:micronaut-http-validation:$micronautVersion",
        ivy"io.micronaut.serde:micronaut-serde-processor:2.9.0",
        ivy"io.micronaut.validation:micronaut-validation-processor:4.5.0",
        ivy"io.micronaut:micronaut-inject-java:$micronautVersion"
      )
    )
  }

  def javacOptions = Seq(
    "-processorpath",
    processors().map(_.path).mkString(":"),
    "-parameters",
    "-Amicronaut.processing.incremental=true",
    "-Amicronaut.processing.group=example.micronaut",
    "-Amicronaut.processing.module=todo",
    "-Amicronaut.processing.annotations=example.micronaut.*",
  )
}

This example demonstrates how to set up a simple Micronaut example service, using the code from the Micronaut Tutorial.

To preserve compatibility with the file layout from the example project, we use MavenModule, which follows the src/main/java and src/test/java folder convention.

Although Mill does not have a built in MicronautModule, this example shows how easy it is to define it yourself as trait MicronautModule: setting up the annotation processor classpath as a JavaModule and setting up the annotation via javacOptions. Once defined, you can then use `MicronautModule in your build just like you.

The MicronautModule shown here does not implement the full functionality of the micronaut CLI; in particular, support for Micronaut AOT compilation is missing. But it easily can be extended with more features as necessary.

> mill test
...example.micronaut.HelloControllerTest#testHello()...

> mill runBackground

> curl http://localhost:8087/hello
...Hello World...

> mill clean runBackground

Micronaut TodoMvc App

build.sc (download, browse)
import mill._, javalib._

object hello extends RootModule with MicronautModule {
  def micronautVersion = "4.4.3"
  def runIvyDeps = Agg(
    ivy"ch.qos.logback:logback-classic:1.5.3",
    ivy"com.h2database:h2:2.2.224",
  )

  def ivyDeps = Agg(
    ivy"io.micronaut:micronaut-http-server-netty:$micronautVersion",
    ivy"io.micronaut.serde:micronaut-serde-jackson:2.9.0",
    ivy"io.micronaut.data:micronaut-data-jdbc:4.7.0",
    ivy"io.micronaut.sql:micronaut-jdbc-hikari:5.6.0",
    ivy"io.micronaut.validation:micronaut-validation:4.5.0",

    ivy"io.micronaut.views:micronaut-views-htmx:5.2.0",
    ivy"io.micronaut.views:micronaut-views-thymeleaf:5.2.0",

    ivy"org.webjars.npm:todomvc-common:1.0.5",
    ivy"org.webjars.npm:todomvc-app-css:2.4.1",
    ivy"org.webjars.npm:github-com-bigskysoftware-htmx:1.9.10",
  )


  object test extends MavenTests with TestModule.Junit5{
    def ivyDeps = super.ivyDeps() ++ Agg(
      ivy"com.h2database:h2:2.2.224",

      ivy"io.micronaut:micronaut-http-client:$micronautVersion",
      ivy"io.micronaut.test:micronaut-test-junit5:4.4.0",

      ivy"org.junit.jupiter:junit-jupiter-api:5.8.1",
      ivy"org.junit.jupiter:junit-jupiter-engine:5.8.1"
    )
  }
}

trait MicronautModule extends MavenModule{
  def micronautVersion: String

  def processors = T {
    defaultResolver().resolveDeps(
      Agg(
        ivy"io.micronaut.data:micronaut-data-processor:4.7.0",
        ivy"io.micronaut:micronaut-http-validation:$micronautVersion",
        ivy"io.micronaut.serde:micronaut-serde-processor:2.9.0",
        ivy"io.micronaut.validation:micronaut-validation-processor:4.5.0",
        ivy"io.micronaut:micronaut-inject-java:$micronautVersion"
      )
    )
  }

  def javacOptions = Seq(
    "-processorpath",
    processors().map(_.path).mkString(":"),
    "-parameters",
    "-Amicronaut.processing.incremental=true",
    "-Amicronaut.processing.group=example.micronaut",
    "-Amicronaut.processing.module=todo",
    "-Amicronaut.processing.annotations=example.micronaut.*",
  )
}

This example is a more complete example using Micronaut, adapted from https://github.com/sdelamo/todomvc. On top of the MicronautModule and annotation processing demonstrated by the previous example, this example shows how a "full stack" web application using Micronaut looks like:

  • Thymeleaf for HTML templating

  • Webjars for Javascript and CSS

  • HTMX for interactivity

  • Database interactions using JDBC and H2

  • Controllers, Repositories, Entities, Forms

  • A more detailed test suite

Again, the example MicronautModule is by no means complete, but it demonstrates how Mill can be integrated with Micronaut’s annotation processors and configuration, and can be extended to cover additional functionality in future

> mill test
...example.micronaut.LearnJsonTest...
...example.micronaut.TodoTest...
...example.micronaut.TodoItemMapperTest...
...example.micronaut.TodoItemControllerTest...
...example.micronaut.HtmxWebJarsTest...

> mill runBackground

> curl http://localhost:8088
 ...<h1>todos</h1>...

> mill clean runBackground