Great International Developer Summit (GIDS)

Simpler Java Build Tools with Object-Oriented Programming - Haoyi Li

52:56 · 21 Apr 2026 – 24 Apr 2026 · YouTube

About this talk

This talk focuses on the challenges associated with Java build tools, particularly Maven and Gradle, which can be cumbersome to use despite Java's reputation for being a straightforward language. The speaker, a seasoned Java developer, discusses common issues faced in small-scale and large-scale Java projects, such as installation, configuration, performance, and IDE support. He introduces Mill, an open-source build tool designed to enhance developer experience by simplifying the build process, improving performance, and offering better IDE integration. Mill eliminates the need for extensive configuration and installation steps, and it allows for easy customization through straightforward programming techniques. The session includes live demonstrations comparing Mill's performance and usability against traditional tools, illustrating significant improvements in build speed and ease of use.

Full transcript

[music] >> Hello everyone. My name is Howie and this presentation is about Java build tools. Um so the motivation for this talk is that although everyone knows that Java is a very easy language to learn with very good performance and very good IDE support, uh Java build tools and Maven and Gradle often don't quite live up to the expectation. Um they're hard to use, very confusing. Uh

they tend to be quite somewhat slow. And IDE support also tends to be not as good as you'd expect from a normal Java application. This talk uh will talk about some of the challenges of these Java build tools both at the small scale as well as at the large scale. I will talk about one of my open source projects called Mill that tries to improve upon some

of these areas. And we'll look at how uh we're able to improve the experience working with these Java builds and Java projects using Mill, uh making it easier and safer and more performant than with traditional tools and Maven and Gradle. So a bit about myself, um I've been programming Java for over 20 years now. I do open source projects for the last 10 or 15. Um my

expertise in developer tools, things like CI systems, testing systems, and build systems, which is the topic of today's talk. So first, the challenges of small scale Java builds. Now imagine you have this small Java program. So this is a Java program that you may write in a white board interview or you may write as a small script. So this program is a HTML uh web scraper. It

will it you it uses the Jsoup library to fetch a page from Wikipedia, parses out the links, finds the outgoing pages, and does a breadth-first traversal of this of this Wikipedia article graph, going page by page up to starting from the given start article uh arg0 until the given depth at arc one. So, this is a bit of a synthetic example, but it's representative of the many

ad hoc things that you may do in a small Java program. You talk to the internet, use some third-party libraries, write some basic algorithms, and so on. So, if I wanted to run this code, um it's actually surprisingly tricky. It's easy code to write. Java's not a complicated language, but because there's this third-party dependency, I can no longer just run java htmlscript.java to run my program. Um

what you what you could do is you could run java -cp and go download the jsoup jar from Maven Central and pass it in the class path, but no one's actually going to do that cuz it's very troublesome. So, you probably want a build tool. And that's where the complication starts. So, you start having to ask questions like, you need to make sure you have Java installed,

which version you have, you make sure you're not using Java 8 otherwise the var keyword won't work. Do you need to install SDKMAN or Homebrew or jenv to manage your Java versions? Do you need to install Maven? And once you're done, you tell your Maven compile and download and cache everything and do all that other work for you. But, we're not done yet because let's say I

have this small Maven configuration for jsoup. It has org.jsoup jsoup 1.17.12 as a dependency. And let's say I try running it using maven:exec colon java and passing these exec args. So, it's a bit more of a boilerplate at the command line having to type out this exec colon java [clears throat] thing compared to running python foo.py or bash foo.sh. But, the problem here is not just that

tedious, but also it doesn't yet work. So, it says parameter main class for goal exec Maven plugin not found. Okay, fine. So, turns out that if you ask ChatGPT or Gemini for you, it will tell you you need to add explicit build plugin rather than relying on implicit one with explicit configuration for the main class over here. And you do that, you get a different error, class

not found. Um and so, it turns out we forgot to compile our code. You can't just use Maven exec Java. You need to do Maven compile exec Java or Maven install exec And now we get a different error which says class of our not found. Turns out this error was due to a Java version cuz Maven defaults to Java 8 from 2012 and var keyword only came

out in Java 11 in 2015 or so. So, we put that in and now it finally works. this is kind of is a synthetic example, but this demonstrates why people often don't write small programs in Java. It's not because Java is a complicated language to learn. It's not because Java syntax is hard or Java doesn't have good libraries or Java is this or that. The problem is

that is really in build tool in the Java build tooling that makes it kind of troublesome to run your Java program. So, your things like automation scripts will be written in Python or Bash or student projects will be written in Python nowadays or JavaScript. Uh but but I think even things that do need to be written in Java like I have example for my for my library

I publish. I'm trying to reproduce a bug in the third-party library to submit a bug report. Comes very troublesome to write these small Java programs uh and build them so I can use them and deploy them and send them to different people and run them in different places. So, those are some of the challenges of small-scale Java pro uh build programs. Next, let's look at challenges of

larger-scale Java programs that you deal with in our build systems. For larger-scale problems, the pro- programs, the problem is not so much convenience but things like performance, customizability, and IDE support, all of which are things that become important as our Java uh mono repo code base gets large. So, first, talking about performance. Most people don't think that Java is a very fast language to compile. Like people

say Go is a very fast language to compile, but people won't say the same for Java. If you look at uh large code base like the Netty code base, Netty is a popular open source networking uh framework. It's about half a million lines of code and takes about 50 seconds to a minute to compile in parallel on my laptop. So, I can kick this off now just

to show it to you live. This is the Netty code base. I'm going to run Maven clean and Maven 10x parallel fast install everything to make sure everything gets compiled. Going to take about 50 seconds, so we won't wait for it. If you run Maven to clean compile a single module like the Netty common sub module using {dash} TL common, you see it compiles at about No,

but 4.5 seconds to compile a small module doesn't seem very fast. You keep compiling and you sit back and you wait for a while. What most people don't realize is that most of Java C directly using the command that Maven runs internally like you can ask Maven to print it out using a debug flag. You actually see it runs 4. and 1.5 seconds rather than 4.5 seconds.

Uh so, it's actually Java C is actually quite fast. It's just a build tool that's slow. this you may know that Java C, the Java compiler, is a program written in Java. Right, it runs on the JVM. And like any other program written in Java running on JVM, it has cold starts and only good performance later when it has time to warm up. So, by running it

cold from the command line, 1.5 seconds is actually the worst possible performance you get from a Java compiler. So, what does good performance look like? Well, if I run Java C hot and keep it keep the Java C program in memory, so JVM has time to jit compile the bytecode and optimize and all that stuff, you see that it'll take 0.3 seconds to compile the same 30,000

lines of code that took Maven 4 and 1/2 seconds to compile. So, I can show you all this live. Um earlier I kicked off a clean compile of the whole Netty code base. Took about 1 minute uh plus in parallel on my laptop. If I run a Maven clean compile of the single Netty common subfolder, it'll take about 4 to 5 seconds, probably. So, that's a clean

finishing. Now, it's compiling. Okay, it took about 6 seconds. It might be a bit slower today, but it's fine. If you run Java -cp directly, it'll be so much faster. You know, so it takes um this it took three 3 seconds rather than 6 seconds. Maybe two two point something seconds. So, the performance varies based on what's running on the computer and how you run it, but

it's already much faster than running uh Maven. If I run Java C hot using this command I have here, so first time you see it took 18 seconds. Sorry, 1.8 seconds. So, not particularly fast. But as Java C warms up, you can see the duration drops, which is what you'd expect. The the Java compiler and the JVM and the JVM gets hot, the the JIT compiler hotspot

kicks in, and after a few runs we're now looking at 0.3 seconds rather than uh 4.5 seconds to compile the same code. So, this is compiling code that's something you wouldn't even do day-to-day, but it just shows how much surprising overhead the compiler gets when you're running within a traditional build tool like Maven or Um people expect that maybe build tools have like 10% 20% 50% overhead.

Most people don't expect a build tool to have 1,500% overhead that we are seeing in this uh demonstration. And it's not like we did anything special, right? We didn't rewrite the Java compiler in Rust or anything. We took the same Java compiler, the same Java version, same class file, same source file, same class path, and we just ran it hot without the the Maven overhead, and that's

enough to give us a 15,000 sorry, 1,500% speed up. So, that's the performance angle. So, the next angle is in customizability. So, any large project you need to customize the build like the Netty project we saw earlier has to do some code generation, has to compile some C code for its native bindings, and so on. So, as a a straw man example, let's say we want to

save the project line count as a line count text resource file. So, this is a bit artificial, but it's we need to customize in any large Java code base in your build system. So, doing this in the build tool like Maven or Gradle is not impossible, but it is surprisingly tricky. So, this example here is a implementation using Maven. It looks very reasonable. It uses all code

house module exact Maven plugin. And there's multiple bugs in it that most of you probably not be able to notice. It's all It's all senses to code review. You will probably accept this as a straightforward customization. But turns out to be wrong. It turns out we got to escape our ampersands in the bash script that's in XML. We forgot to escape our greater than sign. And there

are other things that may not you may may want to consider like if any of you use Windows, this doesn't work on Windows cuz Windows doesn't support SH by default and so on. And there are many other ways to do this. You can have an ant script in Maven using the ant run plugin. You have a custom Maven plugin or module. All of which are doable, but

they're surprisingly tricky given how simple this requirement is. Like this requirement should be something you can do with your eyes closed even as a new new graduate or a student. But instead becomes a very rather tricky thing you need to deal with. And Gradle has different sets of problems, but it's also quite tricky. So, here's a Gradle example using a Gradle's Kotlin syntax to define a new

process resource task I call generate line count that then declares some inputs and outputs. And in the do last, it walks the file the source files, counts all the extensions Java, reads the lines, calculates the size, sums them up, writes it to a resource file. So, this again looks straightforward, but again it is wrong. In this case, we had the dependency on on the wrong input folder.

So, we had registered the dependency on source main, but we actually only care about source main Java. So, every time you touch source main config, source main resources, source main templates, and so on, it will rerun this task and slow down your build unnecessarily. this kind of issue that's very difficult to catch during code review and very easy to make both for humans and for AI assistants.

Uh there's a reason why people's build systems often end up slow and flaky for reasons why no one can understand because these bugs are easy to make, they're very hard to review and catch, and so they they will slip in as people work on a new large code base in the build system, and that's that's why people's builds often feel a bit like a mysterious black box

that mostly does what you want, but often not quite. The last problem area I'm going to look at is IDE support. So, if I look at uh for example, this is the Mockito code base. Mockito is a popular open source unit testing framework. It's built using Gradle. This is a the Groovy syntax. You see that that has some basic IDE support for Gradle, like you can see

the type signature. This is a list, inputs is a Gradle task inputs, and so on. So, there's IDE support, you get syntax highlighting. When you actually try to navigate around, like let's say I don't know what JVM args is. I want to see what value was there before I modified I cannot. I get this I I see the little strings, I get the getter and setter, and

nothing else. And it's not unique to JVM args. Anything you try to jump to in Gradle, compiler args, you get a getter and Uh options, you get this minimal java.options, and this doesn't work with type doesn't really tell you what it does. So, basically, although there's some superficial IDE support, it really doesn't help you do what your IDE what you expect IDE to help you with. Like

if I go to any random file in the actual Java code, I can jump to example, assume that, jump to it, and it brings me to the implementation. I can jump to assume assumption by the exception. I can jump to uh other I can jump to matches if I don't know what it does. I can find implementations. So, all this kind of navigation around your code base,

things within third-party libraries that you do in an any normal Java program with the IDE, you cannot do in Gradle or Maven or Bazel or most other the tools. And the reason for that is that although it is still the same IDE, is the same language or similar language Gradle and Java versus pure Java, the real problem here is that things like JVM args are really just

global mutable variables. all of you know that if you use global mutable variables in your main application, your IDE support's going to suck and no one's going to be able to figure out what your application does because global mutable variables confuse both humans and AIs and IDEs the same way. And just because we write we wrap our global mutable variable in a getter and setter, doesn't make

any less global or any less mutable. So, that's why the IDE support in build tools like Gradle or Maven isn't that great, even though it's a great IDE working on a great Java platform. Yeah. So, those are some of the challenges of large-scale Java builds. Well, next I'm going to talk about one shortcomings. So, Mill's an open source build tool that targets Java, Scala, and Kotlin. Uh

it's configured using these YAML files you see here, somewhat like the way that the pom.xml's or build.gradle's you may be used to. It supports more or less everything that Maven or Gradle support, but aims to be faster, easier to use, and generally this more comfortable environment to work with. It's a much smaller project than Maven or Gradle, but there are some people using it out there, but

people contributing and helping maintain. So, a basic Mill build, you say dot slash Mill, it's a bootstrap script similar to Maven or Gradle W. You can say compile, run, test, assembly, and so on. All your normal things. Mill also supports a programmable syntax, which is uh more or less one-to-one mapping with the YAML syntax, except the programmable syntax allows you to add in custom build logic, which

is although it's a bit more verbose, it does give you more flexibility, which we'll explore a bit later why that's useful. And as I mentioned earlier, Mill Maven or Gradle support, all the test frameworks, all the languages, Java, Scala, Kotlin, Android, all the web frameworks, Lift, other the So, that's why Mill is a build tool that does a similar thing to all the other build tools. I

think what's more interesting is where Mill is tries to improve upon all this works from how the traditional Java build tools have functioned. So, first, how how does Mill affect small Java projects? So, the first area that Mill tries to improve upon traditional build tools is that the installation step is much easier. There is no installation step. So, your dot slash Mill bootstrap script that you may

check out with our Git repo or check out with your company's Git repo does all the bootstrapping you need. So, it'll download and cache the Java and JVM installation, whatever version is configured. It'll download and cache Mill itself. It'll download and cache third-party libraries. So, this kind of removes the whole question of like, I clone the Maven repo from GitHub, and 90% of the time it's not

going to work on my computer cuz I have the wrong version of Java, either wrong because too old or you're wrong because of too new. And it's not going to work. I need to deal with GN or SDK and or something to be sorted out to get the right version running. But with Mill, that just never works. It's always at the right version. You'll always use a

version that the project uses, not the version that happens to be randomly installed on your laptop. And whoever checks it out will always have the same version, and it'll always work the same And this also means that Mill works the same on any machine. So, you can say dot slash Mill compile or Mill.bat compile on Windows, and it doesn't matter whether you're a laptop, someone's remote dev

box, whether you're on a Docker container, whether you're on a CI system, it'll always just work, and it'll work the same way without any installation. The next area that Mill tries to improve upon these small Java programs is it really simplifies the build config. Like this XML file is something that you may or may you probably aren't even going to be able to write out yourself without

assistance from Google or ChatGPT. And even you or Google or ChatGPT may make mistakes. Uh with Mill, the the build.mill.yaml file is just dramatically simpler than the Maven XMLs or even Gradle build.gradles and Gradle properties. And the command line syntax is also much simpler just to define this is a Java module with one Maven dependency. I say mill run and pass the arguments. No {dash} exact.args=quotes. No

a big pom.xml with explicit like main class. All these things the build tool should be able to figure out and it does in case I mean it does. So you just write write a Java program. Write tell it it's a Java module with some third party dependencies and you can run it. The last area that's interesting here is mill scripts where if your Java program really is

a small program like a single file, then having a whole file system hierarchy of source main source main Java and a Java file and separate build file somewhere else is very annoying. Like it's annoying to set up. It's also annoying if you want to send it to someone. For example, you're reporting a bug. I have to zip up this whole file system structure and send to someone

which I clean up the target directory so I don't bloat the zip file. So with mill mill allows you to write these scripts. So you can have a single file Java program like htmlscript.java. Put in the build config as a header comment like here. You have seen similar things in Python where you have a Python dependency headers or even YAML files. Sorry, markdown files often have YAML

headers. So this lets us put a YAML header in our Java file and just run it using the YAML header as a build configuration. So for example, over here I have my Java program. Uh this is what we saw on the side earlier. You see the ID and everything just works. Here's my build config using the YAML separate YAML file. Uh and I can do it mill

I say Java one as arguments. And this will do the one one step of the first step of Wikipedia article graph using Jsoup. and I can show you the how this works for scripts. So this is the same program but there's a script, so it has a build header that includes any build config and dependencies. In this case, there's J soup in YAML format. I can do

HTML scraper.java, Java one. To run this script from the command line, so you compile instantly, runs, and there's no separate config you need to set up or separate XML you need to configure and put plugins and all that. You just write your Java code. If If it's really a small program, put the build header, and then you can run it. As mentioned earlier, Nail supports all three

major JVM languages, so I can write HTML scraper.kt as well, uh which is a Kotlin file that implements HTML scraper. Again, with full ID support and everything. Or HTML scraper.scala, which is a Scala file uh that implements HTML scraper. This one requires that I start and dash dash dev. Yep. that's how Nail tries to improve upon the experience writing small Java Um it's not like rocket science,

but just smooths over all the annoyances trying to set up and run a small Java program. That And so, hopefully, rather than writing our small programs in Python, writing them in JavaScript, this gives people opportunity write them in Java. You're using the expertise, using the libraries, using third-party libraries, also you're using the internal code that may all be written in Java. You can now reuse it in

your small programs and scripts without large amounts of friction setting up these separate Maven POMs and remembering how to write configure them and run them. The last section, we'll talk about how Nail uh tries to improve upon the experience for larger Java project Uh again, this comes down to So, the first area I'm going to look at So, to compare performance, we need a project that's built

with two different build tools in the same way. Uh those don't really exist in the wild, so I took the Netty code base and, as an experiment, ported it from Maven to Nail. So, it's again it's half million lines of code 50 sub projects. It took about one or two days to port. Uh we got all the tests passing and then we can run a useful comparison.

So, what we found is that with Mill doing the same thing as you did that previously using Maven, it's usually about four to six x faster. Um so, it's not like 15 x faster that we would hope for as we saw earlier. Maven's about 15 x slower than raw Java C. But, it is still significantly faster than what we were seeing using Maven. So, earlier we saw

that um Maven took about 60 seconds 50 60 seconds to parallel clean compile the whole uh Netty code base. If I go here using Mill, and I do a Mill Mill clean and compile all, you'll see it's somewhat faster. So, the first time running it it might be a bit cold because again Mill's a JVM program, so it also needs to warm up. Uh but, it shouldn't

take 60 seconds. It'll hopefully take about like 10 to 15. Okay, first run took about 50 seconds rather than 60. And second run, it should be a bit warmer and hopefully it'll take about 10-ish. Yeah, about nine nine seconds. So, whether you're doing a clean compile all or parallel clean compile all or even you're you're adding a print line and doing incremental compile, just you know, I

want to print line and add the test and run the test, print line run the test. Uh it is just much faster to do so with Mill than Maven. And it's again not because we're doing anything special, it's just that we've cut down that uh 1 1,500% overhead that Maven adds over Java C. We've cut it down to maybe like 300% overhead, which is still significant, but

definitely much uh much less frustrating than waiting long time long time for Maven build to work. And one interesting area is incremental compiles. So, if I for example make a change in my Netty project, uh let's say I open up Let's say this is my Netty code base. Let's say I open up source main uh source main Java and add like a var somewhere. var x equals

1 Uh and then I do I back new Sorry, new int x equals 1. If I use Mill using Mill here, uh Mill's able to recognize that most of the code in this repo doesn't depend on this addi- addition of this small variable cuz no one's actually using it yet. And so it doesn't need to be recompiled. And so you can see it compiles a very small

number of Java files, like two Java files here, one Java file here, one Java file here. And within about 2 seconds we're we've we've we've recompiled all the necessary parts of the code base and we're done. Uh in comparison, if we use Maven W and run this, you'll find that we end up waiting the full 60 seconds because Maven isn't smart enough to recognize that although I

changed this code, it doesn't really affect the whole rest of the code base. And so Maven does have to go recompile the whole thing and take the whole 60 seconds. So uh that that was Mill versus Maven. Compared to Gradle, we did a similar experiment using Mockito, ported it from Gradle to Mill and just ran the benchmark. We see it's about 3x faster Mill is about 3x

faster than Gradle to do the same thing. So again, it's not like it's 10x faster. We didn't manage to get all the performance that we hoped for out of the Java compiler, but we did manage to cut up Mill does manage to cut off a lot of the overhead that Gradle and Maven impose on top of your traditional build tools. One area Mill's interesting is that every

command you run generates a profile. One of the most common questions people ask with their build tools is why is it taking so long? So with Mill so I'm going to interrupt my Maven build cuz it's going to take a With Mill, if if I write every clean compile I run uh generates a profile that's called your Mill Chrome profile.json. So this file contains metadata about all

the tasks that Mill was starting and finishing. And although you're not you can't really read it manually, what you can do is you can load it into your Chrome colon slash slash tracing UI that all of your Chrome browsers come bundled with. Uh this UI is normally used for benchmarking and profiling uh websites, but you can also use it to profile anything if you provide the right

uh JSON metadata. So, by loading this into Chrome, I I can immediately see where my time is going when I run uh uh key compile all. I can see at the start where it's well utilizing all my CPUs. I can see where it's benchmarked on common.compile. After common compile finishes, it kicks off a few other things, but it then gets benchmarked on buffer and sorry, bottleneck on

buffer and bottleneck on common test compile and so on. So, this is again not something that you cannot do with maybe not Gradle, but something that's sufficiently inconvenient you probably aren't going to do it. You're probably not going to find the right plugin, set up Gradle Enterprise server, and upload your build scans from your local Gradle build. But with Mill, because it's generated by default, makes it

very easy for anyone to just like look at what my build is doing, see where the time is going. If I wanted to, I can decide what I want to do. Like, do I want to uh break up common compile into submodules so I can have parallelism between them? Uh do do what does everyone really need to depend on common test compile, or can some of them

start uh depending on a smaller subset of my repository? this gives you like a lot agency working with a build tool. It's no longer a black box, but it's a rather fast and rather transparent box that you can look into and see what it's doing and decide what you want to do in response to improve your developer experience. So, that's a performance angle. Next angle I'm going

to get is customizability. And so, most build tools require extensions to be published as plugins. Where Mill is interesting is you can just write code to do what you want. Although there often are plugins, you don't need you don't always need them, especially for simple things that are maybe bespoke to your particular code base or your particular organization. You may not always find the right plugin online.

The plugin may not be well maintained. Our plugin may not quite do what you want because it's built for someone else. So, Mill Mill makes it very easy to just do the thing that you need to do and I'll show you what this means. So, over here I have a minimal Mill build using a Mill's uh programmable syntax. So, it's a bit more verbose than the the

YAML's declarative YAML syntax. It's intended for use where you do need customize a build because, for example, in YAML you can't add custom task or custom build logic. Whereas in this programmable syntax you can. So, here we have foo extends Java module. This means that our foo subfolders are Java module. But, let's say and then there's no customizations yet. I'm just using all the default source folder,

default resource folder, default uh compile step and so on. But, let's say I do want to customize it. Let's say I want to count the lines of code in this module and write it to a resource file to you for use at runtime. that's similar to what we did earlier with me and Gradle. Let's see how to do it with Mill. So, with Mill uh every everything

is a is a build task and every build task is a method. Methods are defined using the F. So, I can say def line count is a task. And within this I can call other methods on foo. So, for example, let's say I want to count the lines of code in the source files. So, I need to look at the different source file task I can I

want. It I probably want all source files. Like all source files. I can see this is this all individual source files heading to the select this, call it. I can map it to every file goes to read the lines of that file path .size and sum it up. So, that's all you need to do to define a custom task in Mill. You're just defining a method that's

wrapped in this task annotation or task block that calls other methods and transforms or manipulates the results. In this case I take the list of files, I map it to the size of each line count by each file and sum them up. And now I can do mill show foo.linecount from the command line. And you need to compile the build file first. You'll see it's like 17

lines of code. I can see my output folder, there's this foo/linecount.json file containing its metadata, uh both the value as well as other metadata mill uses to invalidate things. And so now I have my custom line count task. I can even I mean I can inspect foo.linecount and see that we for example we automatically capture the Java doc from the command line, automatically capture the the input

task so you don't need to register them yourself and possibly screw up. This makes it very easy. You call the method, that's a task. But we haven't yet uh added added it to our resource at to as a resource file So in mill everything's a task a build task including resources. All build tasks are methods and the way you customize methods is by overriding them. So you

say override def resources. Again, I'm not sure which way you can ask IntelliJ. as a task, I want to call super resources. I want to call os.write uh to its task.dest folder which I'll explain in a moment uh my line count as a string. I'm going to append uh reference to this to my uh resources.task.dest. So what is this task.dest folder here? So the the purpose of

this task.dest folder is that if I show you this mill show foo.resources, you'll see that I have both the original foo/resources sort folder that is the super resources I kept around as well as the out/foo/resources.task.dest And the purpose of this is in any build tool, the target directory output directory is really like the biggest global mutable variable. Everyone is writing to it, everyone's reading from it. You

don't know who's writing or reading where, and it becomes very easy to mess up. For example, plugins may stomp over each other's files and conflict. Or when you're cleaning, you may clean not enough or may clean too much and causing some break. Mill can't solve the file system being mutable. You still need to read and write files in a build any build tool. But Mill can solve

it being global by saying that every task gets its own destination folder to write stuff, and every other task can only read from it. So, the mutability becomes encapsulated with a global state rather than global mutable state. It has much easier for both humans and the tool to work with. So, now if I look at my out full resource resource of this, you'll see there's a line

count txt file. I can look what's inside, see it's 17. I can do mill food.run and show that it's able to use my resource file at runtime. So, this example again is very artificial. No one actually wants to count the lines of code and pick put print them out in production. Uh but this shows that when you do need to do something custom, maybe something bespoke to

your code base or bespoke to your organization, Mill makes it very easy for you just to do the thing rather than having to go find the line count plugin and try to integrate them or or if if it's well maintained or or if it does the thing you have to do and having to debug some the source code from some random guy off GitHub. Uh with Mill,

you just write the code using normal object-oriented concepts like method definitions, method calls, overrides, and super, all which are concepts that you're all already familiar with working for the last 10, 20 years, whether in Java and Kotlin and Scala and Python and JavaScript. All these keywords work exactly as you'd expect. They let you let you customize your build tool with very familiar object-oriented concepts. So, the last

area I'm going to look at is the IDE support angle. So, you notice the IDE will actually work quite well in these Mill programmable builds. You have not just the documentation, uh but you also I things like the override jumps to overridden declaration. All the auto complete works including things like override. Def and then you see the auto complete pops up. Basically everything works here and it

where where it didn't work well in Gradle, didn't work well in Maven. For example, if I want to see how my uh If you want to see how my result my my fork args, which is the mill equivalent mill equivalent of JVM args result in Gradle. Let's say I pass it like that some flag. Oh When I define this uh the IDE is able to recognize that

this is a override. I can jump to the overridden I can find usages of this. Can see the bunch of usages. I can also see the previous value, which is this empty sequence of strings. And I can navigate around by saying, "Okay, where does fork args get used?" Fork args gets used in this runner object. Runner gets used in run background, run fork task. Run fork task

gets used in run. And the other way works, too. Like, let's say do not find the main classes. Find main class causes find main class opt thing and does some uh matching on its results. Like, look at find main find main class opt. Find main class opt calls main class. If there's something there, it uses it. If there's none, it calls all local main classes. If there's

none, it says that no main class is found. If there's exactly one, it uses it. And if there's not multiple, it says that multiple main classes found. Uh please tell me which one to use. So, the reason why mill can give you this type of IDE experience that mill Gradle can't is that again mill's build both your config as well as absolute upstream config is all built

using these overrides, inheritance, and super. And all of these are things that IDEs are much better at navigating than global mutable variables that Gradle and Maven and Bazel typically use. And that's why mill is able to give you this really smooth IDE experience uh despite being a much smaller and younger build tool. So, uh that's the last section. You better large Java build a mill is faster,

it's easier to you using a configure, it's safer to configure, not so easy to screw up. And the ID helps a lot more when you're navigating a large project build, which is what the key uh happens when you end up with any large Java project. So, this is the last slide. Um I won't read it out to you, but basically the I think the long and short

of it is any of you are interested in Java build tools that wondered if there could be something better than Maven or Gradle, I encourage you to give Mill a try. So, Mill is a much younger project than Maven or Gradle. Hasn't been battle tested for 20 years as Maven has, but there's a lot of interesting ideas. I think these interesting ideas flow into very concrete improvements

in things like the onboarding and setup experience, configuration syntax, the customization experience, ID experience. All of these are things that traditional Java build tools have really struggled with for the last 10 years. I think Mill has been able to make some substantial improvements. So, the website's over here at millbuild.org. There's clear documentation for all three JVM languages. And there's a info email in case any of you

any of you want to ask questions or learn more. Uh thanks and hope you enjoyed the talk. So, did he specifically ask you about this? Why you why you building this build? And that is the idea is just to get to this problem from the just things but even Maven like how to keep one of the all adding a particular dependency and then excluding a couple of

the dependencies coming to the table. It'll make you go one level deep, which is Yes. the dominant dependency down in that particular case. What I'm with I I don't really care dependency and exclude that property dependency we have. Yes. What I did take my bomb here and I have a similar Yes, that that works. We have excludes, we have pinned versions, and we support bomb bomb files

in predefined list of dependencies your whole project will use. Yeah, I think in general there aren't any major gaps. There are some less commonly used plugins that Mill doesn't support yet. Uh for example, Mill doesn't support like the Vaadin framework. It only It only supports Spring Boot, Micronaut, and Quarkus. There's some more uh long tail that doesn't have built-in support for them. You may have to integrate

yourself. Um I think the biggest challenge is for the uh companies or codebases which have their own custom Maven plugins that uh you know, customize Maven to do something unique to your requirements. Uh Mill won't have any equivalent to that, and so you need to do a similar customization in Mill like what I showed you earlier to uh integrate Mill to make it do what you want,

and that can take some time. Uh so that's a I think the biggest challenge for people trying to adopt it so far. Most major features work, but the customizations is always a tricky. Zeke, what what click makes your service pretty by God until eventually at the finish. The dark you know, bumping you to the how dare you be. I mean what I told you when I did

the accident that Maven told me to do. Uh it's similar. So, if you don't pin the version using a dependency management or BOM, it will look at your different versions and it will try and find a compatible version which is typically the lowest version that is equal to a higher than all the That's I think the Mill and Maven and Gradle use that algorithm. If you do

pin a version that's not set to something else, it will get forced to the pinned Uh so yeah, I think it shouldn't be surprising. It works more or less the same as Maven does and uses the same Maven central method data you can publish and depend on Maven central. It works about the same way, including things like compile-time depths, run-time depths, provided, so on. I personally have

the never thought you know, either on the you know, the um any Java project team Java project who they do like ways but getting let us know what how frequently that it export. So, when we say that you only take a minute or something that they will take a minute to get. >> Yeah. In in real life you probably have to build that thing with it. We'll

build a bit. Let's say if it fails or something and we make Jadine. Why do we make changes that we take about say 10 minutes or something? Yeah. Jadine You take that after 15 minutes to go back and then do the big production again plus then read to do it like But when it does take a while for a cabinet it would have get take these and

then I will combine this with the big thing. So, the default time out I think is 30 minutes of inactivity. I think it's configurable if you want to configure it but I think the 30 minutes is a reasonable default. So, if you do if you don't use it for like a whole day it will turn itself off and stop using memory on your computer. Uh but yeah,

so if you go away for 30 minutes and come back it may be a bit slower. But as you saw earlier for example compiling a whole lot of Netty using mail call took 15 seconds. So, even though it's slower than the 9 seconds it took hard to still a lot faster than the 60 seconds that may have been top. So, yes there is some slow down there

is a threshold but it's it's still pretty fast despite that. Yeah. What about the other other item? For example and payment like that was running the unit test say you know more for skipping something for skipping unit test or say publishing I bought other packaged jar for my particular unit test. >> things. Yeah, all all all that works. So, for example if I go to another go

to my script example I have a unit test here for my HTML script a script that depends on HTML script where it extends JUnit 4 to add JUnit 4 support and I can just run this >> [clears throat] >> from the IDE I can run it from the command line. I can pass in tags from the command line if I want to select specific tests or I

can just click on the the JUnit test in the IDE here to run that a test. So, can we work with later because what I Yes. It is that they take me you using the ultimate to build a particular image and then I begin the quality of the same by closing the device gate by plane. As you say great. So, you need that thing I would want

to give you something in this year later tonight in a big way. Yes. Play the lucky you don't like the yellow and they say by what was it going to access for saying you don't I'm being that doctor you made it. Yes. Uh So, in such cases started go why is that ultimate And I can skip the test or I can do the test building, you know,

and I'm building in our product. Uh so, by default uh for one thing one area that mill differs from maven is that tests are separate from build. So, if you say mill uh run, for example, it will not run the test by default. You say mill compile or mill package or mill jar Let's say show the jar server. If I create this jar It does It doesn't

run test by default. Only if you actually actually say mill uh it will then run the test from the command line or in the IDE. I don't think that's the dev in a day to in in this subproject. Yeah, and if you want you have the different build tools like quite different test frameworks like JUnit or and TestNG a different syntax can pass in With to filter

here. I don't remember the syntax off the top of my head. Uh but you can pass in flags to your test framework and test framework, whichever it is will use it. And what package these tests will be driving to the end? No. You can ask for a specific Like You can ask for a separate test.jar which will then contain the test code. Yeah, and so for publishing

in Maven Central normally you don't publish the test only publish the main code and test are running locally. Any other questions? Sorry? Oh, what kind of profiles? Right. Okay, thanks. What? The balance of what's profile saying with the get up and This comes a bit fast. One year and be the what? Yeah, um so, mill it typically works via uh, command line queries. So, for example, I

can do Mill compile. I can do Mill test.compile. So, you select the task from the command line and you can write wild cards. For example, you can see I want wildcard.compile to say compile everything recursively that is can be compiled or everything recursively within foo. So, foo and wildcard.compile. So, you normally don't select profiles like in Maven, but you just but you use uh, queries to say

I want to compile this set, I want to run this set of tests, you know, all tests within this subfolder or this sub-subfolder. Uh, and you can you can pass in environment variables and JVM properties as well, but it's less common. The build tool doesn't care about your uh, your test framework. It may take environment variables that you will then make use of in your tests. What

what problem that uh, this are not the the the the app. Yeah. That is uh, what? There's a catch to statement. Last statement is rather quick. Yes. Then we use the Swagger file to generate our action client. Which is again still a problem. Yes. >> Swagger application. So, we need to we have the son of the factory. Then we need first to go to back end and

find That uses Swagger program. Yes. Build the I need to write them. Yes. >> The line in green double. Back end. Yes. You have a group like it with nice. Yes. Back end service with a different mobile apps. Okay. Two minutes. So, the the the way you the way it works in Mill is that um, typically you you work with a graph rather than phases. So, let

me see if I can find a good example for you. Sorry, do I have a good I don't have a good diagram right now. Actually, I actually, I do have a diagram. It should be in the docs. Uh custom build logic. So, typically, the way Mill works is via the graph rather than via module. So, the graph's at task level. So, to do what you suggested, you'd

have uh similar to what we saw earlier, you'd have a custom task that depends on the compilation that maybe generates the Swagger as part of our annotation processor or something. And then you'd put that in resources. And resources then gets used by the assembly task. So, although you have a graph at the module level, you don't actually have a graph at the task level. So, you would

have you'd have your Swagger generation, so your compilation annotation processor. You'd use that in resources. And resources will be used downstream. And all the caching and parallelism happen automatically, and you don't need to build it twice. Uh because it isn't actually a cycle once you break it down into smaller uh tasks. Whereas, Maven doesn't have a concept of tasks. So, Maven only has modules, and that's why

it gets a bit tricky. Yeah. That is something I've read about. Sure. Glenn says some of the challenges with the product might So, what what these build up Um each a lot of things. Yes. From this standpoint, you saw those kinds of things I and then I'm in a place but then again, we are discussing you know, that Yes. So, the common ones Yes. And the common

ones include ProGuard. So, um Sorry, there's like a list of plugins So, there's a list of plugins that come built in, including ProGuard. So, you can you can turn on ProGuard, and then it will be used to as additional post-compile step in between compile and assembly. You'll then put a ProGuard step there. And it will ProGuard your application. You can you can configure it like shrink and

optimize and obfuscate and entry point flags JS and Yeah, so there's a bunch of them for like Docker, Flyway, GMH and then there's a bigger list of third-party ones that some other people maintain on your internet for their own projects and pay use mail. Yeah, so you might not find everything but the common ones at least I'm going to see here. As I mentioned earlier things that

that things like Bardin or Vertex and stuff that may not be. And then you might have integrated yourself like what I showed you earlier the line count customization. but let the user model I would just like to on my bulk lines and open this or make it They have a software distance for station Yeah, uh you should share the migration instructions which is over here. Give give

them this page and yeah, basically you'll see run the there's the automatic migrator that migrates things like your dependencies and flags and versions. Those are standard things everyone has and then for custom custom plugins like a ProGuard or like Spring Boot you may have to manually turn on the new ProGuard plugin or new Spring Boot plugin or agent may have may may turn that on if you

give them documentation. Cool. Yeah. So bring it in. I spent it. I think I'm done. So could you say that again? I'm stripping in them. Incremental compile Uh we share the incremental compiler with two other build tools. The incremental compiler is called Zinc. Uh and we share it with Basil and SBT. So this is like an open source project. That's like a Java incremental compiler. And yeah,

so I I we didn't need to re-implement it from scratch. So there are two Actually there are two incremental compilers. For Java or Scala projects we use the one called Zinc and for Java plus Kotlin projects we use um the Kotlin build tool incremental compiler. So which I depending on what language you use I pick the correct open source incremental I don't know how it works but

it works. I didn't write it. I wrote it in my bag. Not that like Even I think So, within the same project typically is you have to migrate the whole thing. Uh but what you can do is you can have them living side by side. So, uh you can migrate to mill by just adding the mill files. You don't need to delete the maven files. You don't

need to move your source files around. So, just leave everything there. Leave your maven build Set up mill and you can use both side by side and eventually you can get rid of maven if you decide mill is good enough or get rid of mill if you decide it doesn't work well. Uh another way you can do uh incremental migrations is on a module by module basis.

So, uh mill and uses the same publishing and dependency uh formats as maven. So, publish to maven central, publish to GitHub packages, Artifactory, and so on. So, if you have multiple re- repos that interact through Artifactory server, for example, you can convert one to mill and no one else needs to know and they you know, mill can use the maven artifacts and maven can use the mill

artifacts. So, that's another way you can uh migrate them migrate incrementally rather than all at once. Cool. Does it support multi-module Uh yes, it supports multi-module projects and it'll parallelize things within the modules like automatically and cache them. Is it done Is it done by Uh unit tests uh are incremental within a module granularity. So, if you touch something it'll run all your tests within that module

and downstream modules. Uh we There's work in progress to make it finer grain, but currently we hasn't landed yet to make it file by file. Like that could be done, but we haven't done I'll let it bind you get my there. What's that? You you Some files on this testing track. Yeah, uh so the the way you the way you would do that is you compile all

your Java code, you read the bytecode, see which method calls which other method, and when someone changes some code and reruns the test, you see which methods change and then go follow the graph to see which test could be affected. So, for example, if this helper method changed, who calls helper method, and who calls them, and eventually which test end up being affected. But, let's Uh we

are we are already doing that for some parts of Mill, but not for unit Yeah, it is it's possible. I think Eclipse does it, if I'm not mistaken, and IntelliJ might have something similar internally when you say run test fast IntelliJ will do some analysis. So, we could do the same thing, and I I know how to do it, but we haven't done So, you said now

the SS by using the law of audit dates within the department or the law of something. Where is it stored? Where is the cache located? Yeah, um it all goes in this out folder. So, this out folder contains the caches for uh like the fork arguments, the final main class, which is food.html script uh the we we we ran we created a jar just now, so it

has a reference to the past and part of the jar that we created. Um yeah, so this out folder is where Mill caches everything locally. You delete it, and everything's gone, and you can build clean. You normally don't need to clean very much because we invalidate things more precisely than Maven. Like Maven, you always need to clean install, but Mill usually don't need to clean compile usually,

unless something goes very wrong. I suspect they deeply use up the garden of all those that are all their tests. Is there any other any other thought process open? it you it uses one called libraries cache coursier. So, coursier is a dependency resolver that we use open source. So this contains all the different dependencies you downloaded. It's the same one that again basil and SBT use I

believe. So we use the same But yeah it's a standard is tool that is not mill specific. It uses it both for downloading dependencies that we see here as well as downloading the JVM like I mentioned earlier we will always use a JVM that the project the project is meant to use. So it will download the relevant JVM and you never need to fiddle around with your

JVM or SDK at the command line. Just set the JVM version and we'll pick the right one. If it's not there we'll download and cache Cool. Cool. I think we're out of time. Thanks for all the questions. >> [music]