惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
WordPress大学
WordPress大学
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
IT之家
IT之家
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
美团技术团队
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
Martin Fowler
Martin Fowler
Recorded Future
Recorded Future
H
Help Net Security
aimingoo的专栏
aimingoo的专栏
Y
Y Combinator Blog
博客园_首页
A
About on SuperTechFans
MongoDB | Blog
MongoDB | Blog
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
B
Blog
The Register - Security
The Register - Security
I
InfoQ
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
雷峰网
雷峰网
Last Week in AI
Last Week in AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
N
Netflix TechBlog - Medium
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
有赞技术团队
有赞技术团队
Stack Overflow Blog
Stack Overflow Blog
Jina AI
Jina AI
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News - Newest:
Hacker News - Newest: "LLM"
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
W
WeLiveSecurity
Latest news
Latest news
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 叶小钗
L
Lohrmann on Cybersecurity
博客园 - Franky
Recent Commits to openclaw:main
Recent Commits to openclaw:main
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
How flexible is the Mill build tool?
Florian Mantz · 2026-06-29 · via DEV Community

tl;dr: I implemented an experimental Mill plugin that publishes a Scala library as a pure Java library.

This experimental plugin grew out of my efforts to learn the Mill build tool. I decided to tackle a use case I found challenging:

In the past, before Scala 3 introduced the TASTy format, getting different Scala versions to work together wasn't always easy or possible. In addition, while calling Java functions from Scala has always been intuitive, the reverse (calling Scala from Java) has not always been straightforward. Personally, I think a pure Java library is still the way to go if you want to reach both Java and Scala developers. I asked myself: is it possible to build a pure Java library of a decent size while still using my favorite programming language, Scala? My idea was to create a Java facade for a Scala library, bundle all necessary Scala dependencies within the Java JAR, shrink the output, and shade the third-party Scala classes to avoid name clashes. It sounds like a bold experiment, but I gave it a try and implemented a Mill plugin to support this specific use case. For me, this was a case study to see how customizable the Mill build tool is.

A "hello-world" example project you find here:
https://github.com/fmantz/mill-scala4java-example/tree/main/mill-create-lib

My goal was to automate the core tasks like shrinking, shading, and POM/Ivy adjustments. Users should only need to override methods in exceptional cases. I aimed for a setup that is as simple as a standard Scala project:

//| mill-version: 1.1.6
//| mill-jvm-version: temurin:21
//| mvnDeps:
//| - io.github.fmantz::scala4java_plugin::0.1.0
package build

import mill._, scalalib._, publish._

object `package` extends scala4java.ScalaModule, scala4java.PublishModule {
  def artifactName = "example_lib"
  def scalaVersion = "2.11.12"
  def proguardVersion = "7.9.1"

  def mvnDeps = Seq(
    mvn"com.lihaoyi::scalatags:0.12.0",           // a scala lib
    mvn"org.jsoup:jsoup:1.22.1",                  // a java lib
    mvn"org.apache.commons:commons-lang3:3.20.0", // a java lib
    mvn"org.clapper::grizzled-slf4j:1.3.4"        // scala lib that uses a java lib (slf4j)
  )

  object test extends ScalaTests, TestModule.Utest, scala4java.TestModule(this) {
    def utestVersion = "0.8.2"
  }

  object testjava extends JavaTests, TestModule.Junit5, scala4java.TestModule(this) {
    def jupiterVersion = "5.14.0"
  }

  def publishVersion = "0.1.0"

  def pomSettings = PomSettings(
    description = "MyTest",
    organization = "xyz.test",
    url = "https://github.com/johndoe/test",
    licenses = Seq(License.MIT),
    versionControl = VersionControl.github("xyz", "mytest"),
    developers = Seq(Developer("mm", "John Doe", "https://github.com/johndoe"))
  )

}

Key differences include specifying a ProGuard® version, using a custom module instead of the standard ScalaModule/PublishModule, and adding a dedicated test module. As usual, the Java library can be built and published locally with the following commands:

# For local ivy repo: 
./mill publishLocal

# For local maven repo:
./mill publishM2Local    

How does it work (simplified):

  1. Compilation & Documentation: First, the code is compiled as usual, and the Javadoc is generated for the Java facade classes.
  2. Shaded Assembly: Then, the Mill assembly plugin builds a "pre-assembly" JAR, shading all Scala libraries unless disabled. This pre-assembly also includes test code and libraries, which are excluded from the later "publish" JAR (i.e. mill jar). The file is called a "pre-assembly" because it serves as the foundation for both the "publish" and final "assembly" JARs (i.e. mill assembly). The testing code is added to the pre-assembly for the later verification step. The testing code needs to be added at this stage because the Scala standard library is also shaded. Consequently, the test code can no longer be called on the "pre-assembly" JAR unless the testing code and libraries are adjusted accordingly.
  3. Optimization (ProGuard®): Next, ProGuard® is used (unless disabled) to shrink and/or obfuscate the pre-assembly. We leverage the Mill ProGuard® plugin for this. To avoid manual configuration, we set all local source classes—including tests—as entry points. ProGuard® keep rules are automatically generated for all user-defined classes. However, this step may break the code, as perfect auto-generation of ProGuard® rules is not possible.
  4. Verification: After shrinking, tests are executed against the optimized pre-assembly to ensure everything still functions correctly.
  5. Module Creation: The final JAR module is then built from a subset (!) of these pre-assembly classes. It includes all Scala libraries but excludes Java libraries, testing libraries and testing code. A third-party license file is added to provide information on the bundled libraries (unless disabled).
  6. Dependency Management: Finally, the Ivy and POM files are adjusted: all Scala libraries are removed as dependencies, while the Java dependencies from those removed Scala libs are explicitly added.

To verify that the generated JAR works correctly and can be resolved by build tools, you can test it here:

./mill run 

sbt run

mvn compile exec:java

The aftermath

The plugins default settings are:

def enableShadingScalaFiles = true
def enableShrinkingInBuild = true
def enableObfuscationInBuild = true
def enableLicensesInfo = true

With default settings and no verification tests enabled (i.e., omitting extends scala4java.TestModule(this)), the Java library of the example project above was only 571 KB. Disabling 'LicensesInfo' generation reduced the size further to 505 KB. Adding JUnit tests barely affected the JAR size (increasing it to just 586 KB) since JUnit is Java-based and does not rely on Scala. However, switching to 'uTest' increased the size to 793 KB because the testing library requires additional Scala standard functions (code from the scala-library.jar). Finally, using 'ScalaTest' caused a massive jump to 3.2 MB, as it heavily utilizes the Scala API.

  • no tests: 571 KB
  • only junit: 586 KB
  • only utest: 793 KB
  • only ScalaTest: 3.2 MB

  • no tests + no licence info : 505 KB!

These numbers are highly dependent on your specific project and setup and can vary based on several factors, including the libraries used, the Scala version, and your individual coding style. Nevertheless, I consider this experiment a success: the JAR size of the example project was significantly reduced here (compared to the sum of all needed libraries), and the resulting JAR remains highly versatile, working seamlessly across different setups and Scala versions.

To provide an overview of what is included in the example JAR and how different settings affect the results, I have compiled some additional data:

note: scala4java.TestModule was only enabled for JUnit

  • project classes (compiled. without tests):

    • 83.5K
  • included Scala libraries in publish.jar (total ca. 6.5 MB):

    • geny_2.11-1.0.0.jar: 105 KB
    • grizzled-slf4j_2.11-1.3.4.jar: 7.4 KB
    • scala-library-2.11.12.jar: 5.5 MB
    • scalatags_2.11-0.12.0.jar: 691 KB
    • sourcecode_2.11-0.3.0.jar 125: KB

How large are my jars with different settings?

SA = shade, SI = shrink, O = Obfuscate

Setting publish.jar
all false 6.5 MB
SA 7.1 MB
SI 710 KB
O unsupported
SA,SI 792 KB
SA,O 5.5 MB
SI,O unsupported
SI,SA,O 586 KB

note: In the current implementation, obfuscation requires shading for the publish.jar. Furthermore, please note that both shading and shrinking are required to make the JAR compatible with any Scala or Java version.

Limitations

In the scala4java plugin implementation, Mill's assembly feature is used to create the base for JAR files. Consequently, the resolvedIvyAssembly and upstreamAssembly methods have been overridden. Ultimately, this plugin focuses primarily on "JAR" functionality (i.e. mill jar, mill publishLocal, mill publishM2Local) and treats the assembly (mill assembly) merely as an underlying tool.

Therefore, note a few minor limitations:

  • All test code and resources flagged by scala4java.TestModule(this) will be included in the (pre-)assembly. To avoid potential naming clashes, ensure that you use unique names across different test modules.
  • Additionally, some unnecessary test classes and test library JARs may be included in the assembly. While this can be fixed, it is currently considered a low priority.
  • Using the ProGuard®-optimized code instead of the original code also in the assembly was a design decision. Basically, the plugin reuses the pre-assembly JAR as the assembly JAR, only prepending the usual Mill starter scripts.

Conclusion

Mill is remarkably easy to customize, even for niche use cases like the one described above. Whether publishing Scala libraries as pure Java libraries is advisable remains a different matter. Furthermore, the final JAR size depends heavily on the Scala features used and the effectiveness of the ProGuard® optimization, making size predictions difficult. I'm not entirely convinced of the approach yet, but it was a great exercise. If JAR size is a priority, it is advisable to use either a Java testing framework or a lightweight Scala testing framework for the verification step. This allows ProGuard® to better optimize the Scala standard library classes. Feel free to share your thoughts in the comments!

Huge thanks to all the engineers and maintainers behind the tools powering this plugin, especially Scala, Mill, ProGuard®, and Apache BCEL!

DISCLAIMER:

This plugin has only been tested on two small personal projects (on Linux); therefore, it should be considered experimental and not production-ready. Feel free to try it out on your hobby projects, but please do not publish any resulting artifacts to Maven Central, I doubt they would appreciate it! I accept no liability or responsibility for any legal issues that may arise from using this software.

Note

I separated the example code from the plugin code.

To try the example, just clone the GitHub repository (the plugin code is available on Maven Central):

git clone https://github.com/fmantz/mill-scala4java-example.git

To compile the plugin yourself, clone the GitHub repository:

git clone https://github.com/fmantz/mill-scala4java.git

Enjoy!

Troubleshooting

Even the official Mill example from https://github.com/scala/scala3-mill-example-project is not a library you would want to reuse; I tested the plugin with it. Unfortunately, the autogenerated ProGuard® rules were not sufficient. I needed to add two extra rules. Therefore, I disabled obfuscation first to better understand where the problem was. Then, I extended the autogenerated ProGuard® entry points as follows:

//| mill-version: 1.1.6
//| mill-jvm-version: temurin:21
//| mvnDeps:
//| - io.github.fmantz::scala4java_plugin::0.1.0
import mill._, scalalib._

object examples extends scala4java.ScalaModule {
  def scalaVersion = "3.8.4"
  def proguardVersion = "7.9.1"

  // its easier to work with real names (even the jar will be some bytes larger):
  def enableObfuscationInBuild = false

  // add some extra Progard entry rules:
  def prepareAssemblyProGuardEntryPoints = Task {
    val prefix = shadedRootDir() + "." + shadedSourceCodeId()
    super.prepareAssemblyProGuardEntryPoints() ++
      Seq(
        s"-keep class $prefix.scala.math.BigDecimal { *; }",
        s"-keep class $prefix.scala.concurrent.** { *; }"
      )
  }
}

You can test it here:

git clone https://github.com/fmantz/scala3-mill-example-project

./mill examples.assembly

java -jar ./out/examples/assembly.dest/out.jar

The assembly JAR size dropped from 9 MB to 1.5 MB. Since the scala4java plugin allows you to override any default setting, you should be able to resolve any issues that may arise. Usually, adding a missing ProGuard® rule solves the issue. Alternatively, you can disable ProGuard® entirely:

//| mill-version: 1.1.6
//| mill-jvm-version: temurin:21
//| mvnDeps:
//| - io.github.fmantz::scala4java_plugin::0.1.0
import mill._, scalalib._

object examples extends scala4java.ScalaModule {
  def scalaVersion = "3.8.4"
  def enableShrinkingInBuild = false
  def enableObfuscationInBuild = false
}