{
  "version": "https://jsonfeed.org/version/1",
  "title": "java on Mandeep Gill",
  "icon": "https://avatars.micro.blog/avatars/2026/01/1860726.jpg",
  "home_page_url": "https://mandeepgill.net/",
  "feed_url": "https://mandeepgill.net/feed.json",
  "items": [
      {
        "id": "http://mands.micro.blog/2026/08/31/java-for-an-ai-startup/",
        "title": "Java for an AI startup",
        "content_html": "<h3 id=\"beginnings\">Beginnings</h3>\n<p>Pump Up is my third startup as a technical cofounder.</p>\n<p>I did my PhD using Haskell to build high performance numerical compilers, so at my first startup, NStack, a cloud-based DSL for describing data processing pipelines, Haskell felt like a natural fit. It’s great for building languages, and would help us to ensure program correctness and catch more bugs at compile time. Whilst true, turns out those aren’t the things that really matter at an early stage startup.</p>\n<p>From there, we built Datapane, a data science reporting product, in Python - due to the strength of the Python data ecosystem. However, outside of the data stack, things were not as rosy, it felt like the other extreme from Haskell.</p>\n<p>Third time’s a charm - for Pump Up, built in the AI era, and with AI and agents at its core, we wanted to find a better trade-off.</p>\n<p>Our first Pump Up prototypes were actually in Python, first with Django then Litestar, for the same reason every AI startup reaches for Python: that&rsquo;s where the ecosystem is. We&rsquo;d used Django at Datapane for 5+ years and knew the Python pitfalls well: a bolted-on and inconsistently applied type system, a split sync / async ecosystem, and performance poor enough that we were thinking hard about scaling and infrastructure far earlier than any startup should have to. Python trades speed of execution for speed of delivery, but it&rsquo;s a nonlinear trade, and these days there are stacks that give you both.</p>\n<p>The pull was back to a typed, compiled language. Not all the way back to Haskell though: it gave NStack a beautiful internal streaming data model, but also type-level puzzles and lost afternoons trying to unpack and rewind the order of our monad transformer stack just so we could print a value. Ecosystem concerns abounded, it was no fun having to build our own integrations to Google Cloud when we should have been shipping. Similarly, Python&rsquo;s dynamic nature is genuinely useful in a startup, where everything is malleable and half your code is a week from being rewritten.</p>\n<p>Could we have good performance, a great ecosystem, stronger type-checking, flexibility - the Goldilocks of stacks? We chose Java, and that often gets a double-take, but it&rsquo;s been the best choice thus far.</p>\n<h3 id=\"modern-java\">&ldquo;Modern&rdquo; Java?</h3>\n<p>I last used Java (1.6) when working in finance in the mid-2000s, Apache Struts and JBoss application servers were all the rage. It was pretty painful: XML everywhere, deep inheritance chains, setters/getters, inner classes instead of lambdas, and so on. I’d spend the evenings learning Scheme to unwind.</p>\n<p>Over the past 10 years Java has become a modern language. There has been a move away from mutable object-orientated programming to immutable data-orientated programming. Project Loom (aka virtual threads) and <a href=\"https://openjdk.org/jeps/533\">structured concurrency</a> have made threads both cheap and usable. There are now real sum types and pattern matching, via sealed interfaces and exhaustive switch expressions, paving a way to functional programming, closer to a pragmatic OCaml than pure Haskell. Yet still the deep ecosystem and great tooling remain.</p>\n<p>At Pump Up we now run Java 26 and Spring Boot 4 in production. The backend is a substantial modular monolith written in a functional style. We spawn a virtual thread per agent run, scaling up to millions, each with their own state and lifecycle and written in a top-down blocking style, no async/await calls needed. They are so cheap that we can keep the agents around, simply waiting until an event wakes them.</p>\n<p>A prime example of this new data-orientated functional style can be seen in our event log: a sealed interface over all the events that our agent works with, from “request an approval” to “timeout”. Everything that touches an event routes through pattern matches like this:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"><code class=\"language-java\" data-lang=\"java\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">return</span> <span style=\"color:#66d9ef\">switch</span> (payload) {\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">case</span> Payload.<span style=\"color:#a6e22e\">Note</span> _, Payload.<span style=\"color:#a6e22e\">Assignment</span> _ <span style=\"color:#f92672\">-&gt;</span> recordSimple(payload);\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">case</span> Payload.<span style=\"color:#a6e22e\">ExceptionRaised</span> _, Payload.<span style=\"color:#a6e22e\">Timeout</span> _ when transitionsTo <span style=\"color:#f92672\">==</span> <span style=\"color:#66d9ef\">null</span> <span style=\"color:#f92672\">-&gt;</span>\n</span></span><span style=\"display:flex;\"><span>      recordSimple(payload);\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">case</span> Payload.<span style=\"color:#a6e22e\">Action</span> _, Payload.<span style=\"color:#a6e22e\">ExceptionRaised</span> _, Payload.<span style=\"color:#a6e22e\">Timeout</span> _ <span style=\"color:#f92672\">-&gt;</span>\n</span></span><span style=\"display:flex;\"><span>      recordAndApply(payload, transitionsTo);\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">case</span> Payload.<span style=\"color:#a6e22e\">ApprovalRequested</span> _, Payload.<span style=\"color:#a6e22e\">ElicitationRequested</span> _ <span style=\"color:#f92672\">-&gt;</span>\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#66d9ef\">throw</span> <span style=\"color:#66d9ef\">new</span> IllegalArgumentException(<span style=\"color:#e6db74\">&#34;Requests go through openRequest&#34;</span>);\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#75715e\">// ...and so on for the remaining arms</span>\n</span></span><span style=\"display:flex;\"><span>};\n</span></span></code></pre></div><p>All possible event states are handled; miss one, or add a new one, and the codebase stops compiling. In Java 27, the compiler will <a href=\"https://bugs.openjdk.org/browse/JDK-8367530\">suggest the missing patterns</a>, just like Haskell, taking into account deep structural nesting.</p>\n<h3 id=\"in-usage\">In Usage</h3>\n<p>The biggest issues were verbosity and baggage, for instance: a file per class, setter/getter ceremony, access-modifiers, a lack of qualified imports. Java is almost as old as Python, and best practices have changed a lot in that time. Some of the issues were just rustiness on our part (pun not intended!), and a few months in we briefly discussed switching to Kotlin. We ultimately decided against it, as we don&rsquo;t love how far Kotlin diverges from the direction of the JVM itself (e.g. virtual threads aren’t first-class there), and modern Java keeps closing the gap on its own, with improvements such as <a href=\"https://docs.oracle.com/en/java/javase/25/language/module-import-declarations.html\">module imports</a> and <a href=\"https://docs.oracle.com/en/java/javase/25/language/simple-source-files-and-instance-main-methods.html\">simple source files</a>.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"><code class=\"language-java\" data-lang=\"java\"><span style=\"display:flex;\"><span><span style=\"color:#f92672\">import</span> module java.<span style=\"color:#a6e22e\">base</span>;\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">void</span> <span style=\"color:#a6e22e\">main</span>() {\n</span></span><span style=\"display:flex;\"><span>    IO.<span style=\"color:#a6e22e\">println</span>(<span style=\"color:#e6db74\">&#34;Hello, World!&#34;</span>);\n</span></span><span style=\"display:flex;\"><span>}\n</span></span></code></pre></div><p>We also run a tight <a href=\"https://projectlombok.org/\">Lombok</a> configuration, with an allow-list of a handful of annotations that mainly help with Spring, since records have replaced much else of what it helped with. It’s a bit ugly but fine - I think there&rsquo;s a smaller, neater language hiding inside modern Java, unburdened by the past, similar to how CoffeeScript pre-processed to JavaScript; someone should build it, maybe us, one day<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup>.</p>\n<p>The verbosity also matters less and less these days due to AI assistants. The move to coding agents wasn&rsquo;t a problem, they turn out to be very good at Java. The ecosystem is enormous, so there is plentiful training data. There&rsquo;s usually one obvious way to do things, which suits a model for the same reason it suits a new hire. Slightly verbose beats cryptic: an agent can read and extend plain Java in the way that perhaps it couldn&rsquo;t with some of our Haskell <a href=\"https://hackage.haskell.org/package/lens\">Lenses</a> and <a href=\"https://hackage.haskell.org/package/singletons\">Singletons</a> type-level usage. And the feedback loops are strong: types, along with static analysers like <a href=\"https://errorprone.info/\">ErrorProne</a> and <a href=\"https://github.com/uber/nullaway\">NullAway</a>, give an agent a compile-time check on every change it makes, and JFR and the JVM&rsquo;s instrumentation give it a runtime one.</p>\n<h3 id=\"wrapping-up\">Wrapping Up</h3>\n<p>At NStack I chose Haskell due to my own prior personal preferences, and swung to Python in response to that experience. This time we picked for the business first and have been surprised by how little we gave up and what we gained in return. The choice still raises the odd eyebrow in SF, since the mental model many folks have of Java dates from the <code>FactoryFactory</code> era. But the engineers in the know tend to be exactly the pragmatists we want to hire, which is partly why I&rsquo;m writing this, along with wanting to update perceptions.</p>\n<p>There is a growing middle ground of languages that score highly on the metrics I think many startups care about - speed of writing, flexibility, performance, runtime instrumentation and debugging. C# and Go are in this bucket too, and we considered both: C#&rsquo;s server-side ecosystem isn&rsquo;t as deep, and Go would mean giving up sum types and exceptions for a weaker error handling story, which we weren&rsquo;t willing to do. We chose &ldquo;modern Java&rdquo;, and are super happy with the result.</p>\n<p>There&rsquo;s plenty more to write about here, the agent runtime in particular deserves its own post. If you&rsquo;re building agents on the JVM, or just want to discuss: <a href=\"mailto:mg@mandeepgill.net\">mg@mandeepgill.net</a>, always happy to chat.</p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p>it&rsquo;s often said that Java is a great language, burdened with horrible defaults, this would be a chance to fix that.&#160;<a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;</a></p>\n</li>\n</ol>\n</div>\n",
        "date_published": "2026-08-31T11:29:46-07:00",
        "url": "https://mandeepgill.net/2026/08/31/java-for-an-ai-startup/",
        "tags": ["ai","java","startup"]
      }
  ]
}
