Java Web Project 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
Simple Web Example
This example shows off running a simple webserver in a module configured via build.mill.yaml
extends: JavaModule
mvnDeps: [org.springframework.boot:spring-boot-starter-web:3.2.0]
In this example, the webserver script uses the Spring Boot framework to run the web server
package example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
@SpringBootApplication
@RestController
public class WebServer {
public static void main(String[] args) {
System.setProperty("server.port", System.getenv().getOrDefault("PORT", "8080"));
SpringApplication.run(WebServer.class, args);
}
@PostMapping("/reverse-string")
public String reverseString(@RequestBody String body) {
return new StringBuilder(body).reverse().toString();
}
}
> ./mill runBackground
> curl -d 'helloworld' localhost:${PORT:-8080}/reverse-string
dlrowolleh
> ./mill clean runBackground # shut down webserver
Testing Web Projects
This example web project comes with a small test suite in the test/ folder,
configured by test/package.mill.yaml. This test suite spins up the server and
makes a single HTTP request against and checks it behaves as expected.
extends: [build.JavaTests, TestModule.Junit5]
mvnDeps:
- com.squareup.okhttp3:okhttp:4.12.0
- org.junit.jupiter:junit-jupiter-api:5.10.1
- org.junit.jupiter:junit-jupiter-engine:5.10.1
> ./mill test
Test example.WebServerTests#testReverseString() finished...
Jetty Hello World App
extends: JavaModule
mvnDeps:
- org.eclipse.jetty:jetty-server:9.4.43.v20210629
- javax.servlet:javax.servlet-api:4.0.1
object test:
extends: [JavaTests, 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:${PORT:-8080}
...<h1>Hello, World!</h1>...
> ./mill clean runBackground
Micronaut Hello World App
package build
import mill.*, javalib.*
object `package` extends MicronautModule {
def micronautVersion = "4.6.1"
def mvnDeps = Seq(
mvn"io.micronaut:micronaut-http-server-netty",
mvn"io.micronaut.serde:micronaut-serde-jackson",
mvn"ch.qos.logback:logback-classic:1.5.3"
)
object test extends MavenTests, TestModule.Junit5 {
def mvnDeps = Seq(
mvn"io.micronaut:micronaut-http-client",
mvn"io.micronaut.test:micronaut-test-junit5"
)
// Micronaut test not compatible with running in parallel
def testParallelism = false
}
}
trait MicronautModule extends MavenModule {
def micronautVersion: String
override def bomMvnDeps = Seq(
mvn"io.micronaut.platform:micronaut-platform:${micronautVersion}"
)
override def annotationProcessorsMvnDeps =
Seq(
mvn"io.micronaut.data:micronaut-data-processor",
mvn"io.micronaut:micronaut-http-validation",
mvn"io.micronaut.serde:micronaut-serde-processor",
mvn"io.micronaut.validation:micronaut-validation-processor",
mvn"io.micronaut:micronaut-inject-java"
)
override def annotationProcessorsJavacOptions = super.annotationProcessorsJavacOptions() ++ Seq(
"-Amicronaut.processing.incremental=true",
"-Amicronaut.processing.group=example.micronaut",
"-Amicronaut.processing.module=hello",
"-Amicronaut.processing.annotations=example.micronaut.*"
)
def javacOptions = super.javacOptions() ++ Seq(
"-parameters"
)
}
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 can use any builtin
trait like JavaModule.
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:${PORT:-8080}/hello
...Hello World...
> ./mill clean runBackground
Micronaut TodoMvc App
package build
import mill.*, javalib.*
object `package` extends MicronautModule {
def micronautVersion = "4.4.3"
def runMvnDeps = Seq(
mvn"ch.qos.logback:logback-classic:1.5.3",
mvn"com.h2database:h2:2.2.224"
)
def mvnDeps = Seq(
mvn"io.micronaut:micronaut-http-server-netty",
mvn"io.micronaut.serde:micronaut-serde-jackson",
mvn"io.micronaut.data:micronaut-data-jdbc",
mvn"io.micronaut.sql:micronaut-jdbc-hikari",
mvn"io.micronaut.validation:micronaut-validation",
mvn"io.micronaut.views:micronaut-views-htmx",
mvn"io.micronaut.views:micronaut-views-thymeleaf",
mvn"org.webjars.npm:todomvc-common:1.0.5",
mvn"org.webjars.npm:todomvc-app-css:2.4.1",
mvn"org.webjars.npm:github-com-bigskysoftware-htmx:1.9.10"
)
object test extends MavenTests, TestModule.Junit5 {
override def bomMvnDeps = Seq(
mvn"io.micronaut.platform:micronaut-platform:${micronautVersion}"
)
def mvnDeps = Seq(
mvn"com.h2database:h2:2.2.224",
mvn"io.micronaut:micronaut-http-client",
mvn"io.micronaut.test:micronaut-test-junit5"
)
// Micronaut test not compatible with running in parallel
def testParallelism = false
}
}
trait MicronautModule extends MavenModule {
def micronautVersion: String
override def bomMvnDeps = Seq(
mvn"io.micronaut.platform:micronaut-platform:${micronautVersion}"
)
override def annotationProcessorsMvnDeps = Seq(
mvn"io.micronaut.data:micronaut-data-processor",
mvn"io.micronaut:micronaut-http-validation",
mvn"io.micronaut.serde:micronaut-serde-processor",
mvn"io.micronaut.validation:micronaut-validation-processor",
mvn"io.micronaut:micronaut-inject-java"
)
override def annotationProcessorsJavacOptions = super.annotationProcessorsJavacOptions() ++ Seq(
"-Amicronaut.processing.incremental=true",
"-Amicronaut.processing.group=example.micronaut",
"-Amicronaut.processing.module=todo",
"-Amicronaut.processing.annotations=example.micronaut.*"
)
def javacOptions = super.javacOptions() ++ Seq(
"-parameters"
)
}
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:${PORT:-8080}
...<h1>todos</h1>...
> ./mill clean runBackground
Micronaut Native (GraalVM)
package build
import mill.*
import javalib.*
import javalib.micronaut.MicronautNativeAotModule
import mill.api.{PathRef, Task}
import os.zip.ZipSource
import java.util.jar.JarInputStream
object `package` extends MavenModule, MicronautNativeAotModule {
def micronautPackage = "hello.world"
override def bomMvnDeps = Seq(
mvn"io.micronaut.platform:micronaut-platform:4.10.3"
)
def mvnDeps = Seq(
mvn"io.micronaut:micronaut-http-client",
mvn"io.micronaut.serde:micronaut-serde-jackson"
)
def jvmVersion = "graalvm-community:21.0.2"
def finalMainClass = "hello.world.Application"
def annotationProcessorsMvnDeps = Seq(
mvn"io.micronaut:micronaut-http-validation",
mvn"io.micronaut.serde:micronaut-serde-processor"
)
override def annotationProcessorsJavacOptions = super.annotationProcessorsJavacOptions() ++ Seq(
"-Amicronaut.processing.incremental=true",
"-Amicronaut.processing.annotations=hello.world.*"
)
def javacOptions = super.javacOptions() ++ Seq(
"-parameters"
)
def compileMvnDeps = Seq(
mvn"io.micronaut:micronaut-http-client"
)
override def runMvnDeps = Seq(
mvn"io.micronaut:micronaut-http-server-netty",
mvn"ch.qos.logback:logback-classic"
)
}
This example demonstrates how to configure mill for a simple micronaut native project. It uses Micronaut AOT to optimize the application for the native image generation.
> ./mill show nativeImage
...out/nativeImage.dest/native-executable...
> ./mill show nativeRunBackground
> curl http://localhost:${PORT:-9870}
{"_links":{"self":[{"href":"/","templated":false}]},"_embedded":{"errors":[{"message":"Page Not Found"}]},"message":"Not Found"}
> ./mill clean nativeRunBackground