DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Java

Java is an object-oriented programming language that allows engineers to produce software for multiple platforms. Our resources in this Zone are designed to help engineers with Java program development, Java SDKs, compilers, interpreters, documentation generators, and other tools used to produce a complete application.

icon
Latest Premium Content
Trend Report
Low-Code Development
Low-Code Development
Refcard #216
Java Caching Essentials
Java Caching Essentials
Refcard #400
Java Application Containerization and Deployment
Java Application Containerization and Deployment

DZone's Featured Java Resources

I Built a Java Version Manager by Fixing Other Tools' Open Bugs

I Built a Java Version Manager by Fixing Other Tools' Open Bugs

By David Lerner
Every Java developer knows the ritual. A JAVA_HOME export in one profile file, a different one in another. sdk use java 21 in this terminal, but the other terminal is still on 8. The build passes in your shell and fails in the IDE because the IDE launched from the dock and never sourced your init line. A teammate's "works on my machine" that turns out to mean "works on my shell." I got tired of it, so I built Jolta. It's Volta, but for Java. This article is partly about what it does, but mostly about how I built it, because the process is the part I'd recommend to anyone building in a crowded tool category: I mined my competitors' bug trackers and turned their backlogs into my test suite. The Pitch, in a Paragraph With Jolta, you never think about Java versions again (if you don't want to). brew install, jolta setup, done. Now cd into any project and java, javac, Maven, Gradle, your IDE's run button, git hooks, and CI scripts all use the right JDK for that directory, including on a fresh machine where the pinned JDK isn't installed yet (it fetches it on first use). There's no sdk use, no jenv add, no remembering to switch back. The pin is a plain .java-version file you commit, and it's authoritative everywhere a process can be launched from. The Tech, Briefly Jolta is a single static Rust binary whose shims are the resolver. Each shim (java, javac, jar, and the rest of the JDK toolset) is a symlink back to the binary. Every invocation walks up from its own working directory to the nearest .java-version, picks a JDK from anything on the machine (Jolta's own installs, Homebrew, /Library/Java, the JAVA_HOME_17_X64-style variables CI images set), exports JAVA_HOME, and execs the real tool. Overhead is about two milliseconds. Two design consequences matter more than any feature list. First, there are no shell hooks and no per-shell state, so nothing can go stale. Resolution happens inside the process launch itself, which is why IDEs, cron jobs, and CI steps get the right JDK without any setup. Second, JAVA_HOME is set per-invocation by the shim, so Maven and Gradle daemons, which read JAVA_HOME directly, can't escape it. That second point is an architectural difference, not a quality difference. SDKMAN is shell functions by construction: sdk use mutates the current interactive shell, and that model cannot follow a subprocess into a directory with a different pin. jenv shims per-invocation, but its JAVA_HOME comes from a shell plugin that runs at prompt time; keeping it truthful is its longest-running open issue (jenv #232). The Interesting Part: Building a Test Suite From Other People's Backlogs This is the part worth stealing. Version managers are a mature category. volta, jenv, SDKMAN, mise, and asdf have collectively accumulated a decade of bug reports, each one a user hitting an edge case, already triaged and written up for free. So before writing much code, I mined them in three passes: Their test suites first. Whatever volta asserts in tests/acceptance, whatever jenv checks in its bats files, whatever SDKMAN specs: all of it became conformance cases. If a competitor thought a behavior was worth pinning, it probably is.Then their closed issues. Every fixed bug is an edge case that shipped to users at least once. Each one became a regression test before Jolta could exhibit it.Then their open issues. This is the fun part: bugs that are reported, confirmed, and still sitting in a backlog. I fixed them proactively, in a tool where they'd never been reported because they'd never shipped. A few concrete examples of what that mining caught: Upstream issueThe bugWhat Jolta does insteadmise #9679A wrong-architecture/wrong-libc JDK "installs" cleanly, then every run dies in the loader with a cryptic errorAn exec probe at install time: the JVM must actually run before the install is promotedmise #1887A bare GA release ("21") shadows newer point builds (21.0.x) in resolutionNumeric version keys; a major pin always resolves to the highest satisfying buildmise #6907An early-access build silently satisfies a GA pin (or vice versa)EA and GA are gated: an -ea spec matches only EA, GA specs prefer GAvolta #1183A stray directory at a shim path silently blocks that shim foreverThe shims directory is wholly owned and cleared entry-by-entry on every reshimvolta #2075Downloads aren't checksum-verifiedSidecar SHA-256 verification, including for offline mirrorsjenv #294Shimming a bundled runtime (GraalVM ships node) hijacks the user's other version managersBundled language runtimes are deliberately never shimmedjenv #232JAVA_HOME goes stale and Maven/Gradle bypass the managerPer-invocation JAVA_HOME from the shim, plus a doctor that names exactly what's shadowing what The result is a suite of 300+ regression tests, dozens of them pinned to issues that are still open in other tools' trackers. When someone asks what Jolta does that the others fundamentally can't, this is half the answer: it can't not have these bugs, because they're in CI. The method generalizes. If you're building anything in a category with incumbents, their issue tracker is a prioritized specification of everything hard about the domain, written by your future users. Read it before you write your architecture. If You Do Care What Java You're Running Jolta is fully featured to let you customize your Java versions until your heart is content. Jolta downloads and manages eight distributions (Temurin, Corretto, GraalVM, Oracle, Zulu, Liberica, SapMachine, GraalVM CE) and recognizes twelve for pinning. You can set a preferred vendor: a Corretto shop that pins 11 gets Corretto 11.0.31 even when a higher Temurin build is installed, because vendor preference should beat build-number greed. Exact pins mean exact: 21.0.2 is never quietly satisfied by a neighboring build, and auto-install fetches that exact build. .sdkmanrc files work out of the box for teams migrating. There's an offline mirror mode for air-gapped CI, first-class Windows support (hard-link shims, PowerShell hook, no Developer Mode required), and a jolta doctor whose exit code is the verdict. What It Doesn't Do Jolta manages JDKs, period. SDKMAN also manages Maven, Gradle, and Kotlin; mise manages your whole polyglot toolchain. If you want one tool for node + python + java, use mise. It's good. Jolta's bet is narrower: that Java version switching should be correct from every entry point and invisible the rest of the time. Try It Shell brew install OneAppPlatform/tap/jolta jolta setup The curl one-liner and Windows instructions are in the README. In every category I could find, I tried to make this version manager best in class. Give it a try and tell me if I hit my mark. More
Rethinking Java Design Patterns: From OOP to FP

Rethinking Java Design Patterns: From OOP to FP

By Nicolas Duminil DZone Core CORE
The functional programming answer, to those who wonder how to integrate or combine it with object-oriented programming, is usually: Turtles all the way down. This is an aphorism whose origin is credited to Richard Feynman. In his book, Surely You're Joking, Mr. Feynman !, published in 1985, he tells the story of one of his conferences on the nature of the universe, where he was challenged by someone in the audience, saying that the universe rests on a turtle. Feynman asked then what the turtle is resting on, and the answer was: "another bigger turtle". And when he smugly asked what the bigger turtle is resting on, the attendee said: "It's turtles all the way down, you can't trick me !" This metaphor is often used in the context of functional programming to describe an infinite series of entities governed by a recursive principle. And it's also the answer of functional programming to developers coming from an object-oriented mindset: "just do functional all the way down." But to adopt a more systematic approach to combining object-oriented principles with a functional style, a more practical answer is required, and this is what I'm trying to do here. We, as developers, fortunately don't have to reinvent the wheel. All the problems are solved nowadays, especially since LLM agents became the most common digital infrastructure. But as surprising as it might seem to our younger colleagues, who can't live 48 hours without AI, even before LLMs, a general approach fitting solutions to problems existed, in the form of design patterns. As a matter of fact, object-oriented programming proposes repeatable solutions tested, proven, and formalized, called design patterns, that you most likely already used, even if you aren't aware of it. The Gang of Four classified these patterns into three groups: Behavioral patterns, which deal with responsibilities and communication between objects.Creational patterns that abstract the object creation/instantiation process.Structural patterns that compose objects such that they form larger or enhanced ones. Let's take some of the most commonly used patterns in each category and see how to combine their object-oriented inherent nature with a more functional approach. The Factory This design pattern belongs to the creational category, and its purpose is to instantiate objects without exposing implementation details. The Object-Oriented Approach The figure below shows the class diagram of a factory design pattern: Our scenario here is a simple one: a Product interface implemented by three classes: BookProduct, ElectronicProduct and FashionProduct. They can be created through the ProductFactory class, as follows: Java public class ProductFactory { public static Product newProduct (String name, String description, BigDecimal price, ProductType productType) { Objects.requireNonNull(name, "Name is null"); ... return switch (productType) { case BOOK -> new BookProduct(name, description, price); case ELECTRONIC -> new ElectronicProduct(name, description, price); case FASHION -> new FashionProduct(name, description, price); default -> throw new IllegalArgumentException ("Unknown type: %s".formatted(productType)); }; } } Using this factory, it's very easy to create a BookProduct, for example, while avoiding to expose implementation details: Java ... Product product = ProductFactory.newProduct("Book1", "A book", new BigDecimal("20.50"), ProductType.BOOK); ... As you probably noticed, the ProductType enumerated defines the three categories. If a new product is to be introduced, the factory has to be modified to reflect this business change. And this interdependence of the factory and the enumerated makes the whole approach fragile. In order to reduce this fragility, we need to introduce a compile-time validation with a more functional approach. The Functional Approach Our example is an over-simplified case of a product management system. The presented factory instantiates different simple records having the same arguments. These identical constructors give us the possibility to move the factory directly into the ProductType enumerated, such that any new product automatically requires a corresponding factory. Java enum types are based on constant names, but we can attach to each one its corresponding value. Or, even better, a factory function for creating discrete products. Look at that: Java public enum ProductType { ELECTRONIC(ElectronicProduct::new), FASHION(FashionProduct::new), BOOK(BookProduct::new); public final TriFunction<String, String, BigDecimal, Product> factory; ProductType (TriFunction<String, String, BigDecimal, Product> factory) { this.factory = factory; } public Product newInstance (String name, String description, BigDecimal price) { Objects.requireNonNull(name, "Name is null"); ... return this.factory.apply (name, description, price); } } Now, creating a new Product instances is easier: Java Product product = ProductType.BOOK.newInstance("Book1", "A book", new BigDecimal("20.45")); The public property factory seems redundant now that a dedicated method for the instance creation is available. But it provides a very convenient functional way to interact further with the factory. For example: Java ProductType.BOOK.factory.andThen(showThePrice).apply("Book1", "A book", new BigDecimal("20.45")); as shown in the TestProductFactory class, in the fp_design_paterns.factorypackage. Of course, given that our products need three-argument constructors and since Java doesn't provide an equivalent of the BiFunction class, but with three input arguments, you will need to craft a TriFunction class, as shown below: Java @FunctionalInterface public interface TriFunction<A, B, C, R> { R apply(A a, B b, C c); default <K> TriFunction<A, B, C, K> andThen(Function<? super R, ? extends K> f) { Objects.requireNonNull(f); return (A a, B b, C c) -> f.apply(apply(a, b, c)); } } You can do that or, if like me, you prefer to use a reliable library, then Vavr already defines a Function3 interface that has the behavior you want. Just include the following Maven dependency: XML <dependency> <groupId>io.vavr</groupId> <artifactId>vavr</artifactId> <version>1.0.1</version> </dependency> This library is a good choice if you need to define functions with up to 8 arguments. Then, you just need to replace, in ProductType, the following definition: Java public final TriFunction<String, String, BigDecimal, Product> factory; ProductType (TriFunction<String, String, BigDecimal, Product> factory) { this.factory = factory; } by this one: Java public final Function3<String, String, BigDecimal, Product> factory; ProductType (Function3<String, String, BigDecimal, Product> factory) { this.factory = factory; } The Visitor This design pattern belongs to the behavioral category and its purpose is to add new operations to an existing object hierarchy without modifying the classes of that hierarchy. It is the classic answer to the expression problem: When the set of types is stable, but the set of operations grows, the Visitor lets you keep adding operations cheaply. We reuse the same domain as the factory: a Product implemented by BookProduct, ElectronicProduct and FashionProduct. To give the visitor a reason to exist, each operation now behaves differently per product type: VAT: a reduced 5.5% rate for books, the standard 20% rate otherwise.Shipping: 10.00 + 2% of the price for (fragile, insured) electronics, a flat 3.00 for books and a flat 5.00 for fashion.Discount: 10% for electronics, 5% for books, 15% for fashion. The Object-Oriented Approach The classic Visitor relies on double dispatch. Each Product accepts a visitor and calls back the overload matching its own type: Java public interface Product { ... <R> R accept(ProductVisitor<R> visitor); } public record BookProduct (String name, String description, BigDecimal price) implements Product { ... public <R> R accept(ProductVisitor<R> visitor) { return visitor.visit(this); } } The operation lives in a generic visitor, one `visit` overload per concrete type: Java public interface ProductVisitor<R> { R visit(ElectronicProduct product); R visit(BookProduct product); R visit(FashionProduct product); } Computing the VAT of any product is then a matter of applying a concrete visitor: Java BigDecimal vat = book.accept(new VatVisitor()); Adding a new operation (shipping, discount, ...) only requires a new ProductVisitor implementation as the Product implementation classes never change. This is the reverse of the trade-off the factory made: it made adding a new operation easy, but a new product type is more expensive to add as you must edit its central switch. The visitor makes adding a new operation free but shifts that same cost onto types, since a new product type now forces every visitor to be updated. It is the classic expression problem: you can make types cheap to add or operations cheap to add, but not both. The following figure shows the object-oriented implementation class diagram: The Functional Approach Look now at the class diagram of the Visitor functional style implementation: In modern Java, the functional counterpart of the Visitor is exhaustive pattern matching over a sealed type. We first seal the hierarchy: Java public sealed interface Product permits ElectronicProduct, BookProduct, FashionProduct { ... } An operation is then just a Function<Product, R> built on a switch that deconstructs each record. Because Product is sealed, the compiler proves the switch is exhaustive — no default branch, no double dispatch, no accept: Java public static final Function<Product, BigDecimal> VAT = product -> switch (product) { case BookProduct(String name, String description, BigDecimal price) -> amount(price, "0.055"); case ElectronicProduct(String name, String description, BigDecimal price) -> amount(price, "0.20"); case FashionProduct(String name, String description, BigDecimal price) -> amount(price, "0.20"); }; Being ordinary functions, these operations compose: Java ProductOperations.DISCOUNT.andThen(amount -> "discount=" + amount).apply(fashion); Between the classic Visitor and pure pattern matching sits an intermediate step: the visitor as a bundle of functions, one lambda per type, instead of an interface with one method per type: Java public record ProductVisitor<R>( Function<ElectronicProduct, R> onElectronic, Function<BookProduct, R> onBook, Function<FashionProduct, R> onFashion) { public R visit(Product product) { return switch (product) { case ElectronicProduct e -> onElectronic.apply(e); case BookProduct b -> onBook.apply(b); case FashionProduct f -> onFashion.apply(f); }; } } Which makes an operation a value you can assemble on the fly: Java ProductVisitor<BigDecimal> vat = new ProductVisitor<>( e -> ..., b -> ..., f -> ...); BigDecimal amount = vat.visit(book); The Builder This design pattern belongs to the creational category, like the factory, but it solves a different problem. The factory hides which concrete type gets instantiated, while the Builder assembles a single, complex object step by step, separating its construction from its representation. It is the classic answer to the telescoping-constructor problem: an object with many parameters, among which some are required, most optional, whose constructor would otherwise explode into a combinatorial set of overloads. Our Product records have only three required fields, so they don't motivate a builder. We therefore introduce an Order: a customer order that aggregates the common products as line items and adds several optional attributes: a coupon code, a gift-wrap flag, and a free-text note. Whatever the style, the target is the same immutable value: Java public record Order( String customer, String currency, List<Product> items, Optional<String> coupon, boolean giftWrapped, Optional<String> note) { public Order { Objects.requireNonNull(customer, "Customer is null"); Objects.requireNonNull(currency, "Currency is null"); items = items == null ? List.of() : List.copyOf(items); coupon = coupon == null ? Optional.empty() : coupon; note = note == null ? Optional.empty() : note; } public BigDecimal subtotal() { ... } } The Object-Oriented Approach The figure below shows the class diagram of the object-oriented builder: The classic Gang of Four Builder is a mutable accumulator. The required arguments are captured up front; the optional ones are added through fluent calls that all return this, and build() freezes the accumulated state into the immutable Order: Java public final class OrderBuilder { private final String customer; private final String currency; private final List<Product> items = new ArrayList<>(); private String coupon; private boolean giftWrapped; private String note; public static OrderBuilder of(String customer, String currency) { ... } public OrderBuilder addItem(Product item) { items.add(item); return this; } public OrderBuilder coupon(String coupon) { this.coupon = coupon; return this; } public OrderBuilder giftWrap() { this.giftWrapped = true; return this; } public OrderBuilder note(String note) { this.note = note; return this; } public Order build() { return new Order(customer, currency, items, Optional.ofNullable(coupon), giftWrapped, Optional.ofNullable(note)); } } Building an order reads as a sentence, and you only mention the parts you actually need: Java Order order = OrderBuilder.of("Alice", "EUR") .addItem(book).addItem(phone) .coupon("SUMMER").giftWrap() .build(); The Functional Approach Look now at the class diagram of the functional style implementation: The functional counterpart keeps the same immutable Order target but drops the mutable accumulator. Each build step becomes a first-class UnaryOperator<Order> value, a pure function mapping one immutable Order to the next by returning a modified copy: Java public static UnaryOperator<Order> addItem(Product item) { return order -> new Order(order.customer(), order.currency(), Stream.concat(order.items().stream(), Stream.of(item)).toList(), order.coupon(), order.giftWrapped(), order.note()); } Because the steps are ordinary values, they are not called on a builder, but they are composed with andThen, exactly as the factory composed its factoryfunction and the visitor composed its operations: Java Function<Order, Order> config = addItem(book) .andThen(addItem(phone)) .andThen(coupon("SUMMER")) .andThen(giftWrap()); Order order = config.apply(OrderBuilder.empty("Alice", "EUR")); This is more than a stylistic variation. In the OOP version, a step is a method call that exists only for the duration of the chain. In the FP version, a step is a value that can be stored in a variable, passed to another method, kept in a list of steps and applied later, or reused the very same step twice: Java UnaryOperator<Order> addBook = addItem(book); Order order = addBook.andThen(addBook).apply(OrderBuilder.empty("Alice", "EUR")); The object-oriented Builder wraps a stateful object around the immutable target, while the functional one expresses construction as the composition of pure copy functions over it. "Turtles all the way down", and both land on the same Order. The Decorator This design pattern belongs to the structural category, and its purpose is to attach additional responsibilities to an object dynamically by wrapping it in another object that shares the same interface. It is the flexible alternative to subclassing for extending behavior: rather than a combinatorial explosion of DiscountedTaxedGiftWrappedProduct subclasses, you wrap a product in as many independent decorators as you need, and they stack. We reuse the same Product domain. Each decorator changes the price() and the description() while leaving everything else untouched. To keep the pattern visibly distinct from the visitor, whose rules varied per product type, the decorators here apply the same rule to every product: Discounted: 10% off the wrapped price.Taxed: adds 20% VAT to the wrapped price.GiftWrapped: adds a flat `5.00` wrapping fee. Because they stack, a 100.00 book decorated Discounted → Taxed→GiftWrapped goes 100.00 → 90.00 → 108.00 → 113.00, and its description reads "A book discounted, VAT incl., gift-wrapped." The Object-Oriented Approach The figure below shows the class diagram of the object-oriented decorator: The classic Gang of Four Decorator is an object that implements the component interface and holds a reference to another component, delegating the untouched operations and overriding the ones it enhances. An abstract ProductDecorator captures the delegation once: Java public abstract class ProductDecorator implements Product { protected final Product product; protected ProductDecorator(Product product) { this.product = Objects.requireNonNull(product, "Product is null"); } public String name() { return product.name(); } public String description() { return product.description(); } public BigDecimal price() { return product.price(); } public ProductType type() { return product.type(); } } Each concrete decorator then overrides only what it changes: Java public class Discounted extends ProductDecorator { private static final BigDecimal RATE = new BigDecimal("0.10"); public Discounted(Product product) { super(product); } public BigDecimal price() { return product.price().subtract(amount(product.price(), RATE)); } public String description() { return product.description() + " (discounted)"; } } Since a decorator is a Product, decorators wrap decorators, and the enhancements compose by nesting: Java Product wrapped = new GiftWrapped(new Taxed(new Discounted(new BaseProduct(book)))); BigDecimal price = wrapped.price(); // 113.00 The leaf being wrapped is a BaseProduct, a small record that adapts a shared common.Product into the decorator's own interface. This is necessary because common.Product is sealed and so, exactly like the object-oriented visitor, the decorator cannot make the common records implement its interface directly. The Functional Approach Look now at the class diagram of the functional style implementation: The functional counterpart of a decorator is simply a function which maps a product to an enhanced product and implemented as an UnaryOperator<Product>. Because the common records are immutable, "enhancing" one means rebuilding it through the ProductType factory, already seen at the very beginning, which is why the FP side reuses common directly with no adapter: Java public static final UnaryOperator<Product> DISCOUNTED = product -> product.type().newInstance(product.name(), product.description() + " (discounted)", product.price().subtract(amount(product.price(), "0.10"))); Being ordinary values, the decorations compose with andThen, exactly as the factory composed its factory function, the visitor composed its operations, and the builder composed its steps: Java UnaryOperator<Product> decorate = DISCOUNTED.andThen(TAXED).andThen(GIFT_WRAPPED); Product wrapped = decorate.apply(book); // price 113.00 And, just like the functional builder step, a decoration is a reusable first-class value. For example, the same discount could be applied twice: Java Product wrapped = DISCOUNTED.andThen(DISCOUNTED).apply(book); // 100 -> 90 -> 81 The object-oriented Decorator wraps the component in a stack of objects sharing its interface, while the functional one expresses the very same stacking as the composition of pure Product to Product functions. "Turtles all the way down", and both land on the same enhanced product. The Strategy This design pattern belongs to the behavioral category, and its purpose is to define a family of algorithms, encapsulate each one of them, and make them interchangeable, such that the algorithm may vary independently of the client using it. Where the decorator asked what else should happen to this object ?, the strategy asks which one of these algorithms should be applied ?. We keep the same Product domain and we compute a shipping cost for it. Three interchangeable algorithms are provided: Standard: a flat 4.99 fee.Express: 9.99 plus 2% of the product price.FreeOver: the familiar "free delivery over 50.00" commercial rule. It is parameterized by a price threshold and by the strategy to apply when the threshold isn't reached: should the product price be greater than or equal to the threshold, the shipping is free; otherwise, the product doesn't qualify, and the cost is the one computed by that other strategy. For our 100.00 book, the standard shipping costs 4.99 and the express one costs 11.99. As for the free-over one, with a threshold of 50.00 and a StandardShipping()strategy, the cost is 0.00, since 100.00 is above the threshold. Raising that same threshold to 150.00 falls back to the standard shipping and, hence, the cost is 4.99. Notice that, unlike the visitor, nothing here varies per product type: what varies is the algorithm, and it is the caller that picks it. The Object-Oriented Approach The figure below shows the class diagram of the object-oriented strategy: The classic Gang of Four Strategy declares an interface for the family of algorithms and one class per algorithm: Java public interface ShippingStrategy { BigDecimal cost(Product product); } public class ExpressShipping implements ShippingStrategy { private static final BigDecimal FEE = new BigDecimal("9.99"); private static final BigDecimal RATE = new BigDecimal("0.02"); public BigDecimal cost(Product product) { return FEE.add(product.price().multiply(RATE).setScale(2, RoundingMode.HALF_UP)); } } StandardShipping and ExpressShipping are stateless, their fees being constants. But an algorithm that needs to be parameterized has nowhere to keep its parameters other than instance fields and, hence, becomes a class with state. This is the case of FreeOverShipping, which holds both its threshold and the strategy to fall back to below it, every such pair defining a different algorithm: Java public class FreeOverShipping implements ShippingStrategy { private final BigDecimal threshold; private final ShippingStrategy otherwise; public FreeOverShipping(BigDecimal threshold, ShippingStrategy otherwise) { ... } public BigDecimal cost(Product product) { return product.price().compareTo(threshold) >= 0 ? FREE : otherwise.cost(product); } } Last but not least, the context is the object that uses the algorithm without knowing which one it is. It only holds a reference to the interface, which is what allows the algorithm to be replaced at runtime: Java ShippingCalculator calculator = new ShippingCalculator(new StandardShipping()); BigDecimal cost = calculator.cost(book); // 4.99 BigDecimal total = calculator.total(book); // 104.99 calculator.setStrategy(new ExpressShipping()); cost = calculator.cost(book); // 11.99 total = calculator.total(book); // 111.99 Contrary to the visitor and to the decorator, the strategy doesn't require anything at all from the elements it processes: no `accept` method and no shared component interface. Consequently, and this is the first time it happens on the object-oriented side, the module reuses the sealed common.Product directly, with neither its own hierarchy, nor any adapter. The Functional Approach Look now at the class diagram of the functional style implementation: Of all the patterns seen so far, this is the one where the functional answer is the most radical. The interface ShippingStrategy in the OO implementation declares one single method and holds no state, such that everything it tells us is a Product comes in, a BigDecimal comes out. In functional terms, it is nothing more than a Function<Product, BigDecimal> type. So each algorithm becomes a plain value of the function type, for example: Java public static final Function<Product, BigDecimal> EXPRESS = product -> EXPRESS_FEE.add(product.price().multiply(EXPRESS_RATE).setScale(2, RoundingMode.HALF_UP)); As opposed to the OO side, which required the FreeOverShipping class holding the threshold and the shipping strategy, the FP side captures them in a closure. So this class on the OO side becomes on the FP side a higher-order function, i.e. a function returning the strategy itself: Java public static Function<Product, BigDecimal> freeOver(BigDecimal threshold, Function<Product, BigDecimal> otherwise) { return product -> product.price().compareTo(threshold) >= 0 ? FREE : otherwise.apply(product); } The very same happens to ShippingCalculator, the context class on the OOP side. Its whole reason to exist was to hold a strategy in a field, such that its cost()and total() operations could delegate to it. But a context is just an operation parameterized by an algorithm and this, once again, is precisely a higher-order function. Hence, the ShippingCalculator.total() method becomes: Java public static Function<Product, BigDecimal> totalWith(Function<Product, BigDecimal> strategy) { return product -> product.price().add(strategy.apply(product)); } such that the following call on the OO side: Java ShippingCalculator calculator = new ShippingCalculator(new StandardShipping()); ... BigDecimal total = calculator.total(book); becomes on the FP side: Java BigDecimal total = totalWith(STANDARD).apply(book); There is no field to hold the strategy anymore and, consequently, no setStrategy()method either. Here the strategy is an argument which doesn't need to be stored in the context, just call the function with the right value. But the real advantage of the strategies as ordinary values is that they can be combined. Picking the cheapest of several shipping options requires yet another class on the OO side, while here it's a simple combinator: Java Function<Product, BigDecimal> best = cheapest(STANDARD, EXPRESS); // 4.99 And as usual, they compose with andThen, for example to apply a promotion to whatever cost has been computed: Java Function<Product, BigDecimal> promo = EXPRESS.andThen(cost -> cost.divide(TWO, 2, RoundingMode.HALF_UP)); // 6.00 The OO Strategy encapsulates each algorithm in a class implementing a common interface and injects the chosen one into a context object, while the functional one observes that such an interface describes nothing but a function type which the JDK already provides and, consequently, keeps only the algorithms themselves. "Turtles all the way down", and both compute the same cost. Project Structure The code is organized as a multi-module Maven project. The product domain lives in its own common module: a sealed Product interface, the three product records, and the ProductType enumerated which already carries the FP factory function seen above. Everything that can reuse that domain does: Plain Text oop-fp-design-patterns (parent POM) ├── common sealed Product, the records, ProductType(+factory) ├── factory (→ common) ProductFactory (OOP); the FP factory *is* common.ProductType ├── visitor (→ common) FP: operations over the common records (switch + lambda bundle) │ OOP: its own element hierarchy (see below) ├── builder (→ common) immutable Order over the common records; OOP: fluent │ OrderBuilder; FP: composed UnaryOperator<Order> steps ├── decorator (→ common) FP: composed UnaryOperator<Product> decorations over the │ common records; OOP: its own Product interface (see below) └── strategy (→ common) shipping algorithms over the common records; OOP: the ShippingStrategy hierarchy + context; FP: plain Function<Product, BigDecimal> values The FP factory, the FP visitor and the FP decorator all operate directly on the common records, so nothing is duplicated there, and the Strategy does so on both of its sides. The two exceptions are the object-oriented Visitor and the object-oriented Decorator. The Visitor needs an accept method on every element (double dispatch). The Decorator needs a non-sealed Product interface that its wrappers can implement. In both cases, common.Product is sealed and cannot be extended from another module, so each owns its own element/component types and reuses only the ProductType enumerated. The OOP decorator bridges back to common through a small BaseProduct adapter. This asymmetry is not accidental. The classic Visitor requires every element to expose an accept method, and the classic Decorator requires every component to share the wrappers' interface. Both couple the elements to the pattern's abstraction, so they cannot be the sealed records defined in common. The functional approach has no such coupling: it operates over the sealed type from the outside, pattern-matching for the visitor, rebuilding through the factory for the decorator, so the elements know nothing about the operations applied to them and, hence, can be the shared common records. The Strategy confirms the rule the other way around: it doesn't couple the elements to its abstraction either, only the client to it, and this is precisely why it is the only pattern here whose object-oriented implementation reuses `common` as freely as its functional one. The full code of these examples, including the associated unit tests, can be found here. Have a great summer, everyone! More
Building a Config-Driven SOAP/REST Integration Layer: One Service, Many Protocols
Building a Config-Driven SOAP/REST Integration Layer: One Service, Many Protocols
By Balaji Venkatasubramaniyar
Arrays in Java
Arrays in Java
By Vincenzo Marrazzo
This One Spring Data JPA Pattern Cleaned Up to 3 Years of Repository Debt
This One Spring Data JPA Pattern Cleaned Up to 3 Years of Repository Debt
By Ramesh Bellamkonda
The Java Story: The Official Documentary Is Here
The Java Story: The Official Documentary Is Here

The Java Story | The Official Documentary provides more than a retrospective on one of the most influential programming languages. Tracing Java’s journey from the Oak project at Sun Microsystems to its widespread adoption in enterprise systems, embedded devices, and global platforms, the documentary highlights how technical constraints, strategic decisions, and architectural choices shaped its evolution. For those interested in technology history, it serves as a valuable case study of how software transitions from experimentation to infrastructure, and how early engineering decisions continue to impact modern systems. The documentary’s significance reaches beyond the Java community. It examines enduring themes in software engineering, including portability, backward compatibility, platform design, ecosystem governance, standardization, and the interplay between technology and community. It also reframes innovation as an ongoing process of adaptation, compromise, and collaboration, rather than a single breakthrough. As such, The Java Story is recommended viewing for anyone interested in how technologies endure, evolve, and become foundational to entire industries. From Oak to Open Source Java originated in 1991 as Oak, a component of Sun Microsystems’ Green Project. Initially intended for consumer electronics, it was redirected to the emerging Web when that market did not materialize. Its promise of portability, later described as “Write Once, Run Anywhere,” challenged the idea that software must be tied to a specific operating system. Java’s growth coincided with the browser wars. Netscape partnered with Sun to integrate Java into its browser, while its own scripting language, first called Mocha and later LiveScript, was renamed JavaScript to leverage Java’s popularity. Although the two languages were not technically related, their names reflected strategic alliances as Sun and Netscape sought to limit Microsoft’s influence over desktop software and the Web. The unofficial phrase “In a world without fences, who needs Gates?” captured the spirit of this competition. Java was not initially open source by today’s licensing standards. The term open source was coined in 1998, three years after Java’s public release. However, Java introduced concepts aligned with the movement, including portability, publicly available specifications, shared implementations, and an ecosystem beyond a single vendor. Over time, the Java community became a leading force in open source. Projects such as Tomcat, Maven, Eclipse, Hibernate, and Spring demonstrated that open collaboration could include individual developers, universities, foundations, startups, and global companies. The release of OpenJDK under the GNU General Public License in 2006 completed this transition, establishing Java as both an open-source platform and a model for long-term collaboration among competing organizations. What Makes Java, Java Open source is no longer unique among programming languages, as many now publish their source code and accept community contributions. Java distinguishes itself by combining open-source implementation with open standards. This means Java is not only modifiable code, but also a platform defined by public specifications, compatibility requirements, and shared governance. This model supports a broad vendor ecosystem. Java enables multiple Java Virtual Machine implementations, development tools, distributions, cloud platforms, and enterprise runtimes. The same approach applies to Java EE and Jakarta EE, where independent vendors implement shared specifications. As a result, Java is not tied to the priorities or decisions of a single company. The Java Community Process is central to this structure. Through Java Specification Requests, the JCP offers a formal process for proposing, reviewing, and standardizing platform changes. While this approach may be slower than single-organization models, it ensures transparency, compatibility, and long-term stability. The combination of open source and open standards is a key strength of Java. It enables companies to compete through their implementations while collaborating on the platform. More broadly, the JCP serves as a model for technologies and organizations aiming to balance innovation, governance, vendor diversity, and sustainable evolution. Java’s Impact on the Software Industry Java did not invent the virtual machine, garbage collection, or object-oriented programming, but it helped introduce these concepts to mainstream commercial software development. Previously, programming languages were closely tied to specific operating systems and hardware. Java shifted this paradigm by making the Java Virtual Machine the primary execution target and promoting the promise of “Write Once, Run Anywhere.” This model showed that a virtual machine could deliver portability while still allowing ongoing performance improvements. Innovations like just-in-time compilation, adaptive optimization, and advanced garbage collectors made the JVM a highly optimized runtime. Java’s success also encouraged languages such as Kotlin, Scala, Clojure, and Groovy to adopt the JVM as their execution platform. Java also shaped software design, testing, and documentation practices. JUnit established automated unit testing as a standard and inspired the broader xUnit framework family. JavaDoc made generating reference documentation from source code routine. Additionally, Java books, design patterns, and community practices promoted object-oriented principles to a generation of software engineers. While Java was initially associated with object-oriented programming, the platform expanded to support multiple paradigms. Features such as generics, annotations, lambda expressions, functional-style streams, reflection, and concurrency APIs enabled developers to use object-oriented, functional, declarative, and metaprogramming techniques. Java’s broader impact lies not only in its features but also in how it reshaped expectations for portability, managed runtimes, testing, documentation, and language evolution. Becoming Part of the Java Story Being included in the documentary, even briefly, is deeply meaningful to me. The footage is from when I received a JCP Award, but the moment’s significance extends well beyond the award itself. Since Java 8, I have served on the JCP Executive Committee, participated in several Java Specification Requests, and contributed to discussions that shaped the evolution of the Java platform. Open source and Java transformed my understanding of software, community, and the possibilities of a technical career. Through the Java community, I learned from those who created and shaped the platform. I improved my software design and implementation skills, expanded my professional network, accessed opportunities beyond my local market, and built an international career. I also contributed to the transformation of enterprise Java into Jakarta EE, helping to shape the next generation of specifications for cloud-native and enterprise applications. This journey developed more than just my technical skills. Community participation taught me to communicate complex ideas, write clearly, speak publicly, collaborate across cultures, and contribute constructively amid differing opinions and interests. These skills enabled me to participate in both implementation work and in technology boards and strategic discussions that shape the future of platforms and standards. Open source also encouraged me to improve my English, learn new languages, and build friendships that have become like family. I encourage you to watch the documentary, but do not stop there. Join a Java user group, attend a conference, contribute to an open-source project, participate in a specification, or start a conversation with someone in the community. Java’s story was built by those who chose to participate, and its next chapter will be written the same way. Conclusion Studying Java’s history offers insight into how software has transformed society. Java shaped not only programming languages and enterprise systems, but also the infrastructure supporting business, government, communication, finance, education, and daily digital services. Its story shows that software engineers do more than write code. By building systems, standards, and communities, they help shape how the world functions. I hope this documentary inspires you as it did me. My journey with Java began around Java 8, when I joined the JCP Executive Committee and learned firsthand about the decisions, people, and challenges that shaped the platform before my involvement. I am grateful this history is now shared in such an engaging way. Watch the documentary, explore the community, and consider joining us — not only to understand Java’s history, but to help shape its future.

By Otavio Santana DZone Core CORE
Stop Writing If-Else Spaghetti: Architecting Cleaner Java with the Strategy Pattern
Stop Writing If-Else Spaghetti: Architecting Cleaner Java with the Strategy Pattern

In high-volume, enterprise Java applications, business logic has a natural tendency to degrade into procedural complexity. You start with a straightforward task, such as calculating a discount for a pharmacy claim or evaluating a financial transaction. And before long, the core service method transforms into a multi-hundred-line monolith choked with nested if-else branches and brittle switch statements. This code smell is more than just an eyesore; it creates significant technical debt. It is exceptionally difficult to unit test, violates fundamental object-oriented design principles, and introduces severe regression risks where adding a single business rule threatens to break three existing ones. When evaluating software architecture, a foundational principle stands clear: If you are explicitly checking an object's type or status flag to determine how to execute business logic against it, your code is violating encapsulation. In a modern, cloud-native architecture, software should be open for extension but closed for modification (The Open-Closed Principle). The most elegant weapon for achieving this balance is the Strategy Design Pattern. The Anti-Pattern: Procedural Control Flow Consider a standard enterprise implementation of a pharmacy claim discount calculator. A junior approach typically relies on conditional routing strings hardcoded into the execution path: Java public class LegacyClaimService { public double calculateDiscount(Claim claim) { if (claim.getType() == null) { return 0.0; } // Brittle conditional routing if (claim.getType().equals("SENIOR")) { return claim.getAmount() * 0.20; } else if (claim.getType().equals("VETERAN")) { return claim.getAmount() * 0.15; } else if (claim.getType().equals("CHRONIC_CARE")) { return claim.getAmount() * 0.10; } else { return 0.0; } } } Every time the business team introduces a new discount category, an engineer must manually check out this core service file, append a new conditional branch, alter the monolithic execution path, and run a full regression test suite across every single unrelated discount type. This is an operational bottleneck that scales poorly. Refactoring Pattern 1: Functional Enums for Lightweight Strategies For stateless, mathematical, or rule-based routing, Java Enums can be combined with Functional Interfaces to build highly optimized, self-contained strategy catalogs. By declaring an abstract interface and passing Java 8+ lambdas directly into the enum constants, we cleanly encapsulate the logic exactly where it belongs. First, define the explicit behavioral contract: Java @FunctionalInterface public interface DiscountStrategy { double apply(double amount); } Next, implement the strategy blueprint within a structured Enum, incorporating a defensive lookup mechanism to protect against system crashes: Java import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; public enum ClaimDiscount implements DiscountStrategy { SENIOR(amount -> amount * 0.20), VETERAN(amount -> amount * 0.15), CHRONIC_CARE(amount -> amount * 0.10), DEFAULT(amount -> 0.0); private final DiscountStrategy strategy; ClaimDiscount(DiscountStrategy strategy) { this.strategy = strategy; } // Static optimization cache to prevent continuous array cloning via values() private static final Map<String, ClaimDiscount> LOOKUP_MAP = Arrays.stream(values()) .collect(Collectors.toMap(ClaimDiscount::name, e -> e)); /** * Defensive lookup pattern to prevent runtime IllegalArgumentExceptions */ public static ClaimDiscount fromType(String type) { if (type == null) { return DEFAULT; } return LOOKUP_MAP.getOrDefault(type.toUpperCase(), DEFAULT); } @Override public double apply(double amount) { return this.strategy.apply(amount); } } With this infrastructure in place, your core orchestration service simplifies down to a readable, self-documenting implementation: Java public class ModernClaimService { public double getFinalPrice(Claim claim) { return ClaimDiscount.fromType(claim.getType()) .apply(claim.getAmount()); } } Refactoring Pattern 2: Spring-Managed Component Strategies While functional enums work perfectly for stateless calculations, production enterprise applications frequently require strategies that interact with stateful infrastructure, such as querying external databases, invoking REST clients, or accessing cloud caches. For these heavy, stateful operations, you can combine the Strategy Pattern with Spring's dependency injection framework to build a dynamic plugin registry. Define the Stateful Contract Java public interface ComplexValidationStrategy { boolean validate(Claim claim); String getStrategyName(); } //Step 2: Implement Component Strategies import org.springframework.stereotype.Component; @Component public class AdjudicationValidationStrategy implements ComplexValidationStrategy { // Spring automatically injects required infrastructure beans locally private final DatabaseRepository repo; public AdjudicationValidationStrategy(DatabaseRepository repo) { this.repo = repo; } @Override public boolean validate(Claim claim) { return repo.checkAdjudicationHistory(claim.getClaimId()); } @Override public String getStrategyName() { return "ADJUDICATION"; } } @Component public class CoPayValidationStrategy implements ComplexValidationStrategy { @Override public boolean validate(Claim claim) { // Stateful co-pay validation logic goes here return claim.getAmount() > 0; } @Override public String getStrategyName() { return "COPAY"; } } Architect the Dynamic Strategy Registry Spring natively supports injecting all implementations of an interface directly into a collection. By using a configuration bean or a service constructor, you can map these components programmatically into a map lookup: Java import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @Service public class ClaimValidationOrchestrator { private final Map<String, ComplexValidationStrategy> registry; // Spring auto-injects every class implementing ComplexValidationStrategy into this List public ClaimValidationOrchestrator(List<ComplexValidationStrategy> strategies) { this.registry = strategies.stream() .collect(Collectors.toMap( ComplexValidationStrategy::getStrategyName, Function.identity() )); } public boolean executeValidation(String strategyType, Claim claim) { ComplexValidationStrategy selectedStrategy = registry.get(strategyType.toUpperCase()); if (selectedStrategy == null) { throw new IllegalArgumentException("No valid strategy registered for type: " + strategyType); } return selectedStrategy.validate(claim); } } Production Engineering Considerations Avoiding Performance Pitfalls with Enum.values(): In high-concurrency processing environments, avoid calling Enum.values() or Enum.valueOf() directly within incoming execution loops. Every call to MyEnum.values() forces the JVM to allocate a brand-new array footprint under the hood to preserve array mutability. Always utilize a static, pre-cached map lookup to ensure 0(1) constant-time performance overhead.Granular Unit Testing: By decoupling your validation or execution logic into separate strategy classes or functional constants, you can bypass heavy integration testing bootstrap processes. You no longer need to spin up a complete Spring Boot web context or use complex Mockito frameworks just to verify a simple business calculation rule. Each strategy variant can be verified via isolated, fast-running unit tests.Concurrency and Thread Safety: When utilizing Spring-managed component strategies, keep in mind that Spring beans are singletons by default. Ensure your strategy implementations remain entirely stateless regarding the request context. Pass all volatile transaction data strictly through the method parameters rather than class-level fields. Architectural Strategy Matrix Feature Legacy If-Else Routing Strategy Pattern Architecture Code Readability Low (Choked with Spaghetti loops) High (Clean, self-documenting layers) Extensibility Path Risky (Modifies compiled source files) Safe (Appends isolated classes/constants) Testing Footprint Complex (Requires mocking massive contexts) Minimal (Simple, targeted unit verifications) Execution Performance Linear degradation via String comparison Optimized hash map map lookup () Framework Integration Procedural conditional structures Native inversion-of-control compliance Summary Clean programming isn't defined by how much complex code you can fit into a single method; it is defined by how much code you can safely extend without rewriting existing foundations. By extracting chaotic conditional business rules out of your core services and encapsulating them into interchangeable, modular strategies, you build a system designed for change. This level of true architectural decoupling is the secret ingredient that transforms standard microservice components into resilient, enterprise-grade production platforms.

By Rahul Tewari
How to Build Living AI Coding Assistants With Quarkus Agent MCP
How to Build Living AI Coding Assistants With Quarkus Agent MCP

AI code generation tools are fantastic at writing isolated snippets of code, but they quickly fall short when they need to understand a running application's state. When a compiled class fails, or a local database container drops, standard AI coding assistants are left guessing. They lack runtime context, environment visibility, and any real-time connection to your active local development workspace. The Model Context Protocol (MCP) bridges this gap by standardizing how AI applications interact with local tools. By leveraging the standalone quarkus-agent-mcp server, you can turn your local AI coding companion into a "living" pair programmer that can build, configure, and debug your Quarkus applications in real time. Why AI Needs a Standalone Agentic Connection Standard code assistants operate entirely out-of-band. They read your static source files and generate code based on pre-trained patterns. They cannot interact with your running JVM, read console logs, or probe your local environment. This creates a tedious loop of copying terminal errors, pasting them into a chat window, receiving speculative fixes, and repeating the cycle. While Quarkus offers an in-app Dev MCP server, it suffers from a fundamental limitation: it lives inside the running application process. If your code fails to compile or crashes on startup due to a missing bean, a bad database migration, or a broken dependency, the application dies — and the in-process MCP server dies with it. The agent loses its connection and is left completely blind. The quarkus-agent-mcp server solves this by running as a completely standalone, always-available process. It wraps your active quarkus dev session as a managed child process. If the application crashes, the agent server survives, allowing the AI to inspect the compiler output, diagnose the error, and execute a fix. Core Capabilities of the Quarkus Agent By exposing a standardized control plane to tools like Claude Code, IBM Bob, Cursor, and GitHub Copilot, the standalone agent server unlocks powerful automations: Project scaffolding: The quarkus_create tool can build a brand-new application from scratch. You can tell your agent to "create a Quarkus REST API with PostgreSQL," and it will select the right extensions, bootstrap the build system, and start dev mode automatically.Lifecycle control: The agent can programmatically start, stop, and restart your dev mode applications. It handles Maven and Gradle wrappers seamlessly under the hood.Dev MCP proxying: The standalone server proxies calls directly to the internal Dev UI. This allows the AI to trigger unit tests, inspect exposed REST endpoints, and manage dev services.Semantic documentation search: Instead of making up imaginary APIs, the agent can use semantic search (quarkus_searchDocs) to parse local, pre-indexed documentation. Markdown ┌──────────────────────────────────────────────────────────┐ │ Your IDE with AI assistant │ └────────────────────────────┬─────────────────────────────┘ │ Local JSON-RPC via MCP ▼ ┌──────────────────────────────────────────────────────────┐ │ Quarkus Agent MCP (Standalone Server) │ └────────────────────────────┬─────────────────────────────┘ │ Process Mgmt & Dev UI Proxy ▼ ┌──────────────────────────────────────────────────────────┐ │ Your Running Quarkus App │ └──────────────────────────────────────────────────────────┘ Bootstrapping Your Agentic Setup With JBang Getting started with quarkus-agent-mcp is incredibly straightforward, especially if you use JBang. You do not need to compile custom helper jars or configure complicated environment paths. You can boot the local MCP server directly from your terminal: Shell jbang quarkus-agent-mcp@quarkusio --port 8080 --project-dir ./my-quarkus-app Once the server is running, you can connect your preferred AI agent. For instance, if you are using Claude Code, you can register the local tool server using standard input/output transport: Shell claude mcp add quarkus-agent -- jbang quarkus-agent-mcp@quarkusio The editor automatically discovers the tool mappings, allowing the agent to safely read, modify, and manage your local workspace. Skills Before Code: Guiding Your Assistant One of the most powerful paradigms introduced by the Quarkus Agent is "skills before code". An agent can read specific extension skills via the quarkus_skills tool to learn optimal development patterns, common pitfalls, and testing practices before writing a single line of Java. You can define custom, domain-specific skills using a simple SKILL.md markdown file in your repository. This acts as the source of truth for the AI assistant, ensuring it follows your team's architectural standards instead of guessing. Java package com.example.skills; import jakarta.enterprise.context.ApplicationScoped; import io.quarkus.mcp.runtime.annotations.Tool; @ApplicationScoped public class CorporateArchitectureSkills { @Tool(name = "scaffold_rest_endpoint", description = "Generates a standardized, secure Quarkus REST resource.") public String scaffoldRestEndpoint(String entityName, String path) { return """ package com.example.api; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.transaction.Transactional; @Path("%s") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class %sResource { @POST @Transactional public void create(%s entity) { // Enterprise persistence patterns } } """.formatted(path, entityName, entityName); } } When the AI assistant needs to create a new endpoint, it bypasses generic web training data, references your corporate skill, and directly calls your custom tool. The result is clean, company-compliant Java code on the first attempt. Real-World Scenario: Automated Crash Recovery Let’s trace a common development headache: a failing database migration. Imagine you are working on a service that depends on a PostgreSQL database. You write a new JPA entity, but make a typo in your Liquibase migration file, causing the Quarkus application to crash on startup. Normally, this halts your momentum. You have to hunt down the stack trace in your terminal, locate the broken SQL block, search the documentation, fix the typo, and rebuild. With the standalone quarkus-agent-mcp managing your workspace, the recovery loop is entirely automated: Surviving the crash: While the application fails and shuts down, the standalone agent server remains running.Locating the bug: The agent recognizes the crash and calls the Dev MCP proxy tool devui-exceptions_getLastException. This returns a clean JSON payload containing the exception class, the exact error message, and the specific file path.Applying the fix: Using the precise error location, the assistant opens the Liquibase migration file, corrects the syntax, and saves it.Restarting and verifying: The agent calls quarkus_start to reboot dev mode. It monitors the application log stream (quarkus_logs) to verify that the database connection successfully initializes and the app is ready for testing. Managing Workspace Security and Privacy A common concern with local agentic execution is security. Because the server is standalone and interacts over a secure stdio channel or local loopback HTTP interface, your code remains private. The Quarkus Agent MCP runs entirely on your local machine. It does not harvest telemetry or transmit your source code to third-party endpoints. Network outbound calls are strictly gated — limited only to querying Maven Central for public extensions, pulling documentation updates, or fetching dependency updates. You have complete authority over which local tools you expose, creating a secure sandbox for AI pair programming. Summary The local developer experience is evolving rapidly. By integrating the standalone quarkus-agent-mcp with your environment, you move far beyond basic code completion. You create a highly collaborative assistant that understands your running JVM, leverages local documentation, and can actively recover from application crashes. This integration proves that modern Java with Quarkus is uniquely suited to lead the future of agentic AI development. Check out more from my series here.

By Daniel Oh DZone Core CORE
Hardening MCP Gateways: Mitigating July 28 Security Risks in Java Applications
Hardening MCP Gateways: Mitigating July 28 Security Risks in Java Applications

The upcoming release of the July 28 Model Context Protocol (MCP) specification is a massive milestone for AI integration. By shedding the baggage of stateful connections and embracing a streamlined, stateless HTTP paradigm, MCP has finally become enterprise-ready. Developers can now build highly scalable, decentralized AI tool networks that integrate directly with enterprise data. However, statelessness and flexibility come with a distinct set of trade-offs. The newly introduced capabilities — specifically custom _meta payload objects, dynamic parameter routing, and x-mcp-header mapping — have opened up novel, highly sophisticated attack vectors. If your AI agents can execute code, query databases, or access internal APIs, security cannot be an afterthought. When building these tool gateways in Java, Quarkus provides the ideal framework to defend your infrastructure. Its reactive architecture, strict build-time validation, and enterprise-grade security integrations allow you to intercept, sanitize, and authorize requests before they ever touch your business logic. The New MCP Threat Landscape: Understanding the Attack Vectors In older stateful models, security was heavily reliant on the transport layer and long-lived socket authentication. With the new stateless paradigm, every incoming HTTP request is self-contained. While this makes load-balancing trivial, it shifts the entire security burden to the application layer. Attackers targeting MCP implementations generally exploit three primary vulnerabilities: 1. Protocol Confusion and Header Desynchronization The July 28 specification allows client tool arguments to be mapped directly to HTTP headers (using the x-mcp-header format). This is highly convenient for routing, but it presents a serious risk of "Protocol Confusion." If an attacker manipulates the client-side host to send an HTTP header that contradicts the JSON-RPC payload in the request body, a naive server might authorize the request based on the header but execute a completely different, unauthorized command payload. 2. Metadata Injection via _meta To support stateless sessions, tracing, and custom client context, the new protocol allows client hosts to pass arbitrary JSON data within a _meta parameter. If your Java backend trusts this metadata blindly — for instance, using it to route requests, dynamically construct database queries, or populate system logs — you open the door to classic injection attacks, log forging, and remote code execution (RCE). 3. Privilege Escalation through Prompt Manipulation Large language models (LLMs) are notoriously susceptible to prompt injection. If an attacker tricks an LLM into calling your Quarkus-hosted tool with altered parameters, the LLM will act as a proxy attacker. Without strict validation boundaries at the Java API layer, the LLM can execute commands with the privileges of your application's service account. Markdown [Attacker] ──(Prompt Injection)──> [LLM Host] ──(Manipulated Payload)──> [Quarkus MCP Server] ──(Unauthorized Access)──> [Internal Database] ▲ [Strict Validation Boundary] Defensive Strategy 1: Strict Input Sanitization with Bean Validation The first line of defense is ensuring that no unvalidated data ever reaches your database or internal services. Quarkus integrates seamlessly with Hibernate Validator (Jakarta Bean Validation), allowing you to define declarative, bulletproof rules directly on your data transfer objects (DTOs). Because LLMs can easily generate unexpected, highly erratic JSON payloads, your DTOs must enforce strict limits on string lengths, formats, and unexpected fields. Here is an example of a hardened tool-execution payload model: Java package com.example.mcp.security; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; public class HardenedCustomerLookupArguments { @NotNull(message = "Customer ID is required") @Size(min = 8, max = 12, message = "Customer ID must be between 8 and 12 characters") @Pattern(regexp = "^CUST-[0-9]{4,8}$", message = "Invalid Customer ID format") private String customerId; @Size(max = 100, message = "Context query exceeds maximum allowed length") @Pattern(regexp = "^[a-zA-Z0-9\\s,._-]*$", message = "Query contains forbidden special characters") private String traceContext; // Getters and Setters public String getCustomerId() { return customerId; } public void setCustomerId(String customerId) { this.customerId = customerId; } public String getTraceContext() { return traceContext; } public void setTraceContext(String traceContext) { this.traceContext = traceContext; } } By enforcing alphanumeric patterns and explicit length boundaries, you block malicious payloads (such as SQL injection snippets or directory traversal paths) long before your application logic processes them. Defensive Strategy 2: Blocking Desync Attacks With Reactive Filters To prevent protocol confusion, you must guarantee that the incoming HTTP headers perfectly match the JSON-RPC execution arguments. Quarkus's reactive architecture allows us to write non-blocking ContainerRequestFilter implementations that intercept the HTTP request, extract the payload, and perform this crucial validation step at the gateway boundary. The following filter verifies that the Mcp-Name HTTP header exactly aligns with the method called in the JSON-RPC body, rejecting any mismatched requests: Java package com.example.mcp.security; import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.ext.Provider; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.MediaType; import java.io.ByteArrayInputStream; import java.io.IOException; import io.vertx.core.json.JsonObject; @Provider @McpSecureBoundary public class McpHeaderValidationFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { String mcpHeaderMethod = requestContext.getHeaderString("Mcp-Name"); if (mcpHeaderMethod == null || mcpHeaderMethod.isBlank()) { abortWithBadRequest(requestContext, "Missing required Mcp-Name header"); return; } // Read and buffer the entity stream for validation byte[] bodyBytes = requestContext.getEntityStream().readAllBytes(); requestContext.setEntityStream(new ByteArrayInputStream(bodyBytes)); try { JsonObject bodyJson = new JsonObject(new String(bodyBytes)); String bodyMethod = bodyJson.getJsonObject("params").getString("name"); // Mitigate Protocol Confusion: Both values must align perfectly if (!mcpHeaderMethod.equals(bodyMethod)) { abortWithBadRequest(requestContext, "Protocol Desync: Header and body method mismatch"); } } catch (Exception e) { abortWithBadRequest(requestContext, "Malformed JSON payload"); } } private void abortWithBadRequest(ContainerRequestContext context, String message) { context.abortWith(Response.status(Response.Status.BAD_REQUEST) .type(MediaType.APPLICATION_JSON) .entity("{\"error\": \"" + message + "\"}") .build()); } } Defensive Strategy 3: Zero-Trust Authentication via OIDC and OAuth 2.1 Because MCP tools execute high-privilege actions on internal systems, you must verify the identity of the invoking agent host. The July 28 specification recommends OAuth 2.1 paired with Proof Key for Code Exchange (PKCE) for the authorization flow. Quarkus provides first-class support for securing endpoints with OpenID Connect (OIDC). By importing the quarkus-oidc extension, you can easily turn your stateless MCP server into a secure resource server that validates JSON Web Tokens (JWTs) issued by enterprise identity providers like Keycloak, Okta, or Microsoft Entra ID. Enforcing security is simple. First, define the configuration in your application.properties: Properties files quarkus.oidc.auth-server-url=https://identity.your-enterprise.com/realms/mcp-realm quarkus.oidc.client-id=mcp-gateway-service quarkus.http.auth.permission.mcp.paths=/mcp/v1/* quarkus.http.auth.permission.mcp.policy=authenticated Then, secure your execution endpoints using standard Java annotations: Java package com.example.mcp; import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import io.smallrye.mutiny.Uni; @Path("/mcp/v1") public class SecureMcpResource { @POST @Path("/tools") @RolesAllowed("ai-agent-role") public Uni<McpResponse> executeSecureTool(McpRequestPayload payload) { // This logic is completely secured under OAuth 2.1 return Uni.createFrom().item(new McpResponse("Authorized data access achieved.")); } } Summary The transition to a stateless Model Context Protocol represents a massive architectural leap forward, but it demands an equally sophisticated security posture. Unsanitized metadata, header-to-body desynchronization, and injection vulnerabilities can easily turn a powerful AI assistant into a severe liability. By taking advantage of Quarkus’s robust security landscape — including declarative validation, reactive filters, and native OIDC support — Java developers can comfortably build hardened, production-ready MCP gateways that protect critical enterprise assets, enforce access control, and mitigate modern AI-driven threats. Check out more from my series here.

By Daniel Oh DZone Core CORE
Mitigating Cache Stampedes in Dynamic API Translation Using Java 21 Virtual Threads
Mitigating Cache Stampedes in Dynamic API Translation Using Java 21 Virtual Threads

The Hidden Cost of API Versioning Hell Continuous API evolution is non-negotiable in contemporary software development, yet maintaining backward compatibility remains an incredibly expensive and labor-intensive hurdle. Core schema mutations frequently force downstream enterprise clients into disruptive and unplanned refactoring cycles, stalling product velocity. The typical industry fix — maintaining multiple, hard-coded API routes (e.g., /v1, /v2) — inevitably results in severe codebase sprawl, fractured engineering focus, and massive technical debt for the API provider. To break this cycle, this article outlines raqs (Response Agnostic Query System): a novel, dynamic proxy architecture designed to eliminate client-side disruption entirely. By intercepting traffic and executing on-the-fly schema transformations, raqs allows legacy clients to request data against deprecated contracts while the core upstream backend remains free to evolve. The raqs Solution: A Bifurcated Architecture Running complex natural-language processing or machine-learning inference directly within a high-throughput network routing path is typically a recipe for catastrophic latency. To solve this, raqs splits the network and intelligence layers into two distinct operational planes: The Orchestration Plane (Java 21/Spring Boot): Acting as the primary ingress proxy, this layer intercepts requests, manages multi-tier cache retrieval, handles distributed synchronization, and executes structural JSON transformations. The Inference Plane (Python/FastAPI): Operating as a probabilistic fallback mechanism, this agent calculates semantic and structural relationships between schema keys only when a deterministic mapping rule is missing. Core Architectural Decision Matrix ComponentNaive/Standard Approachraqs ImplementationConcurrency ManagementOS Thread Pooling (Tomcat Defaults) Java 21 Virtual Threads (Project Loom) SynchronizationPolling / Thread.sleep() loop Redisson Distributed Locking (Pub/Sub) Caching TierSingle-node In-Memory Cache Multi-tier (Caffeine L1 + Redis L2) Semantic MappingPure Semantic Models (LLM/Dense Vector) Hybrid Ensemble (Vector + Lexical Distance) Scaling Imperatively With Java 21 Virtual Threads The Orchestration Plane must handle thousands of concurrent client requests while checking caches, holding locks, or awaiting responses from the Inference Plane. The traditional platform-thread pooling model introduces massive operating system overhead and memory footprint under heavy I/O saturation. By building on Java 21 virtual threads (Project Loom), raqs assigns a lightweight, user-mode virtual thread to every single request lifecycle. When a thread encounters an L1/L2 cache miss, it is gracefully unmounted from its underlying OS carrier thread. The carrier thread is freed to handle other active network traffic, while the suspended virtual thread waits to resume once the schema mapping becomes available. This allows us to write straightforward, blocking imperative code that scales out with the efficiency of complex reactive systems. Defeating Cache Stampedes: The "Hero Thread" Pattern A major architectural risk for dynamic proxies is the cache stampede (or thundering herd problem). If a rolling backend deployment instantly mutates 50 schema keys, a burst of 1,000 concurrent client requests will simultaneously experience an L1/L2 cache miss. Without intervention, this triggers a massive wave of redundant, CPU-heavy inference calls that can completely crash the system. We mitigate this by implementing the "Hero Thread" pattern utilizing Redisson distributed locks: Java // Conceptual implementation of the Hero Thread pattern in the Orchestration Plane String lockKey = "lock:schema:" + legacyVersion + ":" + upstreamVersion; RLock distributedLock = redissonClient.getLock(lockKey); // Check L1/L2 cache first MappingRule mapping = cacheManager.getMapping(legacyVersion, upstreamVersion); if (mapping == null) { // Attempt to acquire the distributed lock via Redis Pub/Sub mechanisms if (distributedLock.tryLock()) { try { // The "Hero Thread" has the lock and invokes the Inference Plane mapping = inferenceClient.fetchProbabilisticMapping(legacySchema, upstreamSchema); cacheManager.populateCaches(legacyVersion, upstreamVersion, mapping); } finally { distributedLock.unlock(); } } else { // Non-hero threads are suspended by Loom and wait for cache population mapping = waitForCacheOrRetry(legacyVersion, upstreamVersion); } } return transformJsonPayload(rawResponse, mapping); By enforcing this structure, exactly one thread (the "Hero Thread") takes the computational penalty of invoking the ML Inference Plane. The remaining 49 or 999 concurrent threads are cleanly suspended by Loom, waking up via Redis Pub/Sub to read the finalized, cached ruleset. Pragmatic AI: Why "Pure Semantic" Models Fail During initial prototyping, we found that relying solely on dense vector embeddings (like Cosine Similarity) for short JSON dictionary keys yields dangerous false-positive collisions. For instance, a dense vector model will frequently map the legacy key firstName directly to a new key named lastName because they share highly overlapping linguistic contexts within general training data. To prevent silent data corruption, raqs uses a Hybrid Ensemble Scoring Model that evaluates both semantic meaning and lexical structure: Semantic evaluation: Keys are projected into a vector space using the all-MiniLM-L6-v2 transformer model, calculating Cosine Similarity S_semantic. Lexical evaluation: To account for common developer syntax changes (such as camelCase to snake_case), we compute the normalized Levenshtein distance S_lexical. Through empirical calibration, we fixed the hyperparameters at W_semantic = 0.7 and W_lexical = 0.3. If the combined score fails to clear a strict acceptance threshold (e.g., 0.80), the mapping is rejected. Ensemble Scoring Dynamics in Action Legacy KeyNew KeySemantic ScoreLexical ScoreEnsemble ResultfirstNamefirst_name0.950.88 0.929 (Accept)userIdaccount_id0.820.40 0.694 (Reject)firstNamelastName0.880.55 0.781 (Reject)zipCodepostalCode0.890.60 0.803 (Accept) As shown above, a pure semantic evaluation would have mistakenly accepted firstName as lastName due to its high 0.88 similarity vector. The 30% lexical penalty successfully suppresses the final score below the 0.80 threshold, preserving data integrity. Performance Telemetry and Benchmarks To test the efficacy of this architecture, we subjected the raqs proxy to a load test of 1,000 requests with a concurrency cap of 50, simulating a sudden, zero-knowledge v1-to-v2 upstream schema evolution on a standard CPU-bound host machine. The cold start: Upon initialization against an empty cache, the Redisson distributed lock correctly isolated the thundering herd. Exactly one thread executed the Hybrid ML Inference, completing in 504.65 ms. The blocked threads: The remaining 49 concurrent threads were safely unmounted from OS carrier threads by Loom, waiting for lock release via Pub/Sub and completing with an average latency of 554.24 ms. The steady state: Once the rules were promoted to the Caffeine (L1) and Redis (L2) caches, the subsequent 950 requests bypassed the Inference Plane entirely. The Orchestration Plane achieved an outstanding steady-state processing latency of just 10.25 ms ($\sigma = 2.19\text{ ms}$). This performance distribution demonstrates that the computational cost of machine learning inference can be entirely isolated to cold starts, making real-time, dynamic API translation exceptionally practical for enterprise-scale traffic. The Path Forward API evolution shouldn't force a broken trade-off between breaking client applications or drowning in a versioned codebase sprawl. By pairing the non-blocking concurrency of Java 21 with a highly disciplined, multi-tier distributed proxy, we can build data layers that adapt dynamically to contract shifts. Future iterations of this paradigm will expand beyond simple key mutations to incorporate deep structural payload transformations, JSON path awareness, and automatic data type coercion. Key Takeaways Eliminate versioning sprawl: Engineers can reduce the overhead of traditional API versioning by introducing a dynamic proxy that maps evolving schemas to legacy expectations on-the-fly. Scale imperatively via Java 21: Virtual Threads (Project Loom) allow high-throughput routing middleware to scale using a readable thread-per-request model without heavy reactive frameworks. Implement the "Hero Thread" pattern: Utilizing Redisson distributed locking ensures that expensive schema inference tasks are executed exactly once during high-traffic evolution events. Deploy pragmatic hybrid scoring: Combining dense vector embeddings with normalized Levenshtein distance drastically reduces false-positive mapping collisions. Achieve sub-15ms latency: Decoupling high-latency inference from the routing path ensures that 95% of steady-state traffic experiences near-native performance.

By Aniruddha Chatterjee
Going Stateless: Scaling MCP Servers to Cloud-Native Java and HTTP
Going Stateless: Scaling MCP Servers to Cloud-Native Java and HTTP

The Model Context Protocol (MCP) completely changed how we connect large language models to real-world data and tools. However, early versions of the protocol had a massive bottleneck for enterprise developers: they relied heavily on stateful, long-lived sessions. If you wanted to scale out your AI tools to handle thousands of concurrent agent workflows, you had to deal with sticky sessions, complex load balancing, and heavy memory overhead. The newest updates to the MCP specification solve this problem by introducing a completely stateless HTTP foundation. By removing the traditional initialization handshake and session IDs, MCP servers can now function as lightweight, independent microservices. When you combine this stateless evolution with cloud-native Java, you get the ultimate stack for cloud-native AI infrastructures. Why Stateless MCP Matters for Your Cloud Architecture In older stateful setups, an LLM host maintained an open connection to your server. If that specific server instance crashed or scaled down, the entire context of the conversation loop was lost. The latest specification shifts the paradigm. Every request sent from an AI agent or LLM host to an MCP server is now fully self-contained. The routing relies on two standard HTTP headers: Mcp-Method: Specifies the action (such as executing a tool or fetching a resource)Mcp-Name: Directs the request to the specific tool definition. Because the server no longer needs to remember who is calling it, you can place a standard load balancer in front of a cluster of MCP servers, distribute incoming requests evenly, and scale down to zero when traffic stops. The Cloud-Native Java Advantage: High-Density AI Tools While languages like Python and Node.js are popular in the AI space, they often struggle with heavy production workloads, multi-threading, and deep enterprise integration. Traditional Java solves these enterprise issues but comes with a high memory footprint and slower startup times—making it expensive to run as serverless microservices. This is exactly where cloud-native Java (e.g., Quarkus) shines. By utilizing ahead-of-time (AOT) compilation and GraalVM native images, Quarkus strips away the boilerplate runtime overhead. Plain Text ┌─────────────────────────────────────────────────────┐ │ Traditional Java MCP: ~150MB Ram | 2.5s Startup │ └─────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────┐ │ Cloud-Native Java MCP: ~18MB Ram | 0.015s Startup │ └─────────────────────────────────────────────────────┘ Instead of a single heavy backend trying to host dozens of different LLM tools, you can break your tools into highly specialized microservices. You can deploy a database-lookup tool, an internal API proxy, and a document parser as completely separate cloud-native Java applications. They will start instantly, use less than 20MB of RAM each, and scale up instantly when an AI agent triggers them. Building a Stateless MCP Resource With Cloud-Native Java Implementing a stateless tool in cloud-native Java with Quarkus is remarkably clean. By leveraging the reactive routing capabilities of Quarkus and standard Java objects, you can map the incoming JSON-RPC payloads directly to your business logic. Here is a conceptual example of how a stateless MCP tool controller looks in Quarkus using standard REST annotations: Java package com.example.mcp; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import io.smallrye.mutiny.Uni; @Path("/mcp/v1") public class StatelessMcpResource { @POST @Path("/tools") @Produces(MediaType.APPLICATION_JSON) public Uni<McpResponse> handleToolExecution( @HeaderParam("Mcp-Method") String method, @HeaderParam("Mcp-Name") String toolName, McpRequestPayload payload) { // The request is entirely self-contained; no session lookup required. if ("tools/call".equals(method) && "fetch_customer_data".equals(toolName)) { return executeCustomerLookup(payload.getArguments()); } return Uni.createFrom().item(McpResponse.error("Tool or method not found")); } private Uni<McpResponse> executeCustomerLookup(JsonElement arguments) { // Business logic interacting with reactive databases or internal services return Uni.createFrom().item(new McpResponse("Customer data retrieved successfully.")); } } Summary The combination of a stateless protocol and a cloud-native Java framework removes the operational friction in building enterprise AI features. By deploying stateless MCP servers on cloud native Java - Quarkus, you gain the type of predictable scaling, rapid response times, and bulletproof reliability that modern production environments demand. Check out more from my series here.

By Daniel Oh DZone Core CORE
AGENTS.md Makes Your Java Codebase AI-Agent Ready
AGENTS.md Makes Your Java Codebase AI-Agent Ready

The year is 2026, and the way software is built has fundamentally shifted. We are no longer just writing code for other humans to read; we are building systems that AI coding agents, such as Cursor, GitHub Copilot Agent Mode, Claude Code, and autonomous CLI tools, will navigate, debug, and extend. As Java developers, we are blessed with robust tooling. If you are using Quarkus, you already possess a superpower: Supersonic Subatomic Java with an ultra-fast developer loop, continuous testing, and built-in Dev Services. However, AI agents frequently get tripped up by enterprise Java repositories. They overcomplicate simple architectures, write blocking code where reactive code belongs, or waste tokens trying to spin up manual Docker containers when Quarkus Dev Services could do it out of the box. The fix? AGENTS.md. Let’s explore how to use this emerging open standard to make your Quarkus applications instantly digestible for AI agents. What Is AGENTS.md? The AGENTS.md specification is a tool-agnostic open standard (pioneered by the Agentic AI Foundation) designed to sit at the root of a repository. Think of your standard README.md as human onboarding documentation: it contains high-level architecture narratives, badges, and project philosophy. AGENTS.md, on the other hand, is an executable runtime instruction layer for AI. It is concise, deterministic, imperative, and explicitly structured to prevent "context window bloat" while giving autonomous agents the exact boundaries and commands they need to succeed. The Anatomy of an Agent-Ready Quarkus Codebase When an AI agent initializes inside your workspace, it reads your project structure. Because Quarkus spans both imperative and reactive paradigms, an unguided AI agent will often hallucinate or mix patterns. An effective AGENTS.md for a Quarkus ecosystem must explicitly define three pillars: Operational commands: The exact Maven/Gradle sequences for running, testing, and live-reloading.Architectural boundaries: Strict rules regarding blocking vs. non-blocking code and data access patterns.Infrastructure management: Forcing the agent to utilize Quarkus Dev Services rather than provisioning external databases. Hands-On: The Ultimate Quarkus AGENTS.md Template Drop this exact AGENTS.md file into the root of your Quarkus repository to drastically improve the quality of AI-generated code and autonomous refactoring tasks. Markdown ## Tech Stack & Ecosystem Context - **Runtime**: Java 25, Quarkus 3.x (Supersonic Subatomic Java). - **Build Tool**: Maven (`mvnw` wrapper present). - **Extensions**: REST, Hibernate ORM with Panache, Quarkus Dev Services. - **Database**: PostgreSQL (Managed entirely via Dev Services). ## Critical Operational Commands - **Launch Development Mode**: `./mvnw quarkus:dev` - **Execute All Tests**: `./mvnw test` - **Continuous Testing**: Start `./mvnw quarkus:dev` and press `r` to toggle background testing. - **Production Package**: `./mvnw package` ## Architectural Boundaries & Coding Standards ### 1. Reactive vs. Blocking Rules - Default to **REST**. Endpoints returning `Uni<T>` or `Multi<T>` must NEVER invoke blocking operations. - If a method blocks, annotate it explicitly with `@Blocking`. ### 2. Data Access (Hibernate ORM with Panache) - Use the **Panache Active Record pattern** extending `PanacheEntity`. Do NOT write custom repositories or explicit DAO layers unless complex business logic demands it. - **Transaction Management**: Annotate mutate operations with `@Transactional`. Never manage transactions manually. ```java // Correct Agent Output Example: @Entity public class Developer extends PanacheEntity { public String name; public String specialty; public static Uni<Developer> findByName(String name) { return find("name", name).firstResult(); } } ``` ## Scaffolding Lifecycle for New Microservices When scaffolding a new microservice (e.g., "Scaffold a new microservice for user billing"), the agent follows this deterministic lifecycle: ### 1. Reads the Command Layer - **Bypass manual configuration**: Do NOT generate raw `pom.xml` text by hand, which frequently leads to version mismatches or missing dependency management blocks. - **Use Quarkus tooling**: Rely on the official Quarkus Maven plugin command structure. ### 2. Executes the Tooling - **Command**: Run the explicit `mvn io.quarkus.platform:quarkus-maven-plugin:create` command directly inside your terminal workspace. - **Example**: ```bash mvn io.quarkus.platform:quarkus-maven-plugin:3.x.x:create \ -DprojectGroupId=com.example \ -DprojectArtifactId=billing-service \ -DclassName="com.example.billing.BillingResource" \ -Dpath="/billing" ``` ### 3. Applies Core Extensions - **Guarantee essential extensions** are baked in from the first second: - `hibernate-orm-panache` for data access - `quarkus-rest` for REST endpoints - **Add extensions during creation**: ```bash mvn io.quarkus.platform:quarkus-maven-plugin:create \ ... \ -Dextensions="hibernate-orm-panache,quarkus-rest,jdbc-postgresql" ``` - This prevents the agent from creating legacy or blocking code templates down the line. ### 4. Validates Context - **Transition to Testing**: Once scaffolded, immediately verify that the out-of-the-box generated test suite runs cleanly. - **Validation command**: `./mvnw test` - **Expected outcome**: All generated tests pass without modification, confirming the scaffold is valid and ready for development. ### Post-Scaffold Checklist - [ ] Project structure follows standard Maven layout (`src/main/java`, `src/test/java`) - [ ] `application.properties` contains Dev Services configuration (auto-configured for PostgreSQL) - [ ] At least one REST endpoint exists with a corresponding test - [ ] `./mvnw test` passes cleanly - [ ] `./mvnw quarkus:dev` starts without errors Testing and Local Infrastructure Never manually configure Testcontainers or hardcode local JDBC connections inside application.properties for local development.Rely 100% on Quarkus Dev Services. The PostgreSQL container is automatically spun up during ./mvnw quarkus:dev or @QuarkusTest. Verification Protocol Before declaring a task complete, you MUST: Run ./mvnw compile to ensure zero compilation or annotation processor failures.Run ./mvnw test and confirm all integration tests pass cleanly. Note: Find the solution repository: https://github.com/danieloh30/agents-md-for-java-quarkus.git Shell ### Sample Demo Walkthrough: Put it to the Test To see the power of this setup, let’s imagine a standard demo repository structured as follows: agents-md-for-java-quarkus/src/main/java/com/example/billing/ |____com | |____example | | |____billing | | | |____Invoice.java | | | |____BillingResource.java | | | |____InvoiceItem.java |____pom.xml |____README.md <-- For humans |____AGENTS.md <-- For the AI Agents The Experiment You open this repository inside an AI-native workspace and issue a vague, autonomous prompt: "Add a new REST endpoint to fetch a developer by their specialty, write a test for it, and verify that the app works." Without AGENTS.md The agent might look at pom.xml, realize it's a Java app, and write a legacy, blocking JAX-RS endpoint. It might attempt to spin up a Docker container inside the test via a manual DockerClient or throw an error because it doesn't know how to supply a PostgreSQL URL. With AGENTS.md Reads context: The agent parses AGENTS.md instantly. It recognizes that it must write a reactive Uni<Developer> endpoint using Panache’s Active Record pattern.Generates code: It appends a clean, reactive finder method directly onto the Developer entity.Executes environment: Instead of guessing how to launch your app, it executes ./mvnw quarkus:dev.Leverages dev services: It sees that Quarkus handles the database automatically. It writes a clean @QuarkusTest integration test, triggers the validation, checks the terminal logs, and corrects its own syntax if a compilation check fails. By defining the boundaries upfront, you prevent the agent from writing code that compiles but violates your team's architectural standards. Conclusion: Treat Context as Code Providing an AI agent with free rein over an enterprise Java codebase without boundaries is like letting a junior developer deploy to production on day one without code reviews. By adopting AGENTS.md alongside the rapid developer feedback loops built natively into Quarkus, you bridge the gap between human intent and machine execution. Spend 10 minutes writing an AGENTS.md file today, and unlock massive productivity gains for the agentic future of software development. Check out more from my series here.

By Daniel Oh DZone Core CORE
Compliance Reporting Without Losing the Spreadsheet or the Control
Compliance Reporting Without Losing the Spreadsheet or the Control

Compliance-reporting teams keep spreadsheets in the loop for a practical reason: a workbook lets domain experts inspect assumptions, formulas, source rows, and intermediate values without reading a line of application code. That transparency is genuinely useful, and it's a big part of why replacing Excel outright so often fails to stick. The trouble starts once that workbook becomes part of a repeatable, audited reporting process — a regulatory filing, an IFRS report, a periodic compliance submission. At that point, a shared Excel file isn't enough on its own. What's actually needed is version control, validation, an audit trail, a review step, and a reliable way to connect the spreadsheet's logic to the systems downstream. The spreadsheet itself isn't the problem. It's a review surface domain experts genuinely need. The problem is treating it as a loose file sitting outside the application. The goal isn't to eliminate spreadsheets, but to preserve the spreadsheet experience while letting the application govern how it's used. This article walks through an architecture that keeps the workbook where domain experts can see it, but moves execution — validation, calculation, output generation, logging — into a Java application. The scenario is inspired by a real-world IFRS reporting project, and the same architecture applies to regulatory reporting, statutory filings, actuarial review, and other spreadsheet-driven compliance workflows. The pattern itself doesn't require a specific product: it works with any spreadsheet engine that can load a workbook and expose read/write access to Java, and parts of it apply even if you only use a file library like Apache POI at the edges. Three Ways Teams Usually Respond Rewrite everything in Java. Engineering gets control, tests, and CI. But the calculation logic moves away from the people who understand it. Every threshold change, every new currency, every adjusted formula now goes through a sprint. Sometimes that's correct — if the rules are stable and nobody inspects formulas, do this. For living, business-owned logic, it breeds shadow spreadsheets. Leave the desktop spreadsheet alone. Finance keeps full flexibility. The organization keeps none of the guarantees: no version control, no audit trail, no way to prove which file produced the submitted numbers. Use a file library only at the edges. Java imports the workbook, exports the results. Better — but the correction loop still happens in desktop Excel: download, fix locally, re-upload, re-validate, repeat. Every round trip is an audit gap. Now there is a fourth option: embed the workbook directly into the web application. Domain experts continue working in a familiar spreadsheet interface, while the application governs when users can edit data, when validation runs, which outputs become visible, and how every operation is logged. The rest of this article is about what that looks like in practice. The Big Idea: One Workbook, Two Roles In this pattern, the workbook plays two roles at the same time: For users, it is the interface. They inspect rows, correct values, maintain rule tables, and review generated outputs in a familiar grid.For the application, it is a runtime artifact. Java loads a known template, reads specific sheets and regions, runs validation, writes outputs, and records every run. The design decision that makes this work: Java never wanders through the workbook looking for data. It reads and writes only through agreed sheets and regions — a contract. Finance owns what's inside the regions: values, formulas, rules. Engineering owns the boundary and everything behind it: execution, permissions, persistence, export. Let's see the two stages of a typical reporting workflow through this lens. Stage 1: Let Users Fix Data Issues Without Leaving the App Reporting source data almost never arrives clean. A currency code says US instead of USD. An FX rate is missing. A service fee breaks a policy limit. The template for this stage has two sheets. Input CSV holds the source rows users can inspect and correct. ETL Rule holds the validation rules — as an ordinary spreadsheet table with columns like Field, Check, and Allowed Values. A rule row might say: currency must be one of USD, EUR. Finance can read and change these rules without asking anyone. When the user clicks Run Validation, the application takes over. To make this concrete: the examples in this article use Keikai Spreadsheet, a Java-based spreadsheet UI component, to embed the workbook in the browser and read and write it from Java — though the same three-step logic applies with any comparable engine. Conceptually, the Java service reads the data rows, reads the rule rows, and checks every row against every rule: Java List<SourceRow> rows = sheetReader.readTable(workbook, "Input CSV"); List<Rule> rules = ruleParser.parse(sheetReader.readTable(workbook, "ETL Rule")); for (SourceRow row : rows) for (Rule rule : rules) rule.check(row).ifPresent(report::add); Notice what this is not: the rules are not hard-coded in Java. Java only knows how to read the rule table and apply generic checks. The actual business knowledge — which currencies are allowed, what a valid fee looks like — stays in the workbook where its owners can see it. One detail carries most of the user experience: every validation error records which cell failed — sheet, row, and column. That lets the UI show a panel saying “policy P-1024, field currency, value US, expected USD or EUR” with a link that jumps the user straight to the offending cell. They fix it in the grid, click run again, and validation passes. Compare that to the traditional loop — download, fix in Excel, upload, pray. Here, nothing leaves the system, and every edit can be logged with user, timestamp, old value, and new value. Stage 2: Generate Outputs Under Application Control Once the data is clean, the second stage produces the actual reporting outputs: journal entries, impact tables, export-ready CSV sheets. The input is a policy sheet with assumptions (premium totals, fees, FX rates) plus a rule table that maps accounting events to journal lines. Before the run, the application shows only the input sheet — output sheets stay hidden, because they don't exist meaningfully yet. When the user triggers generation, the same Keikai-backed workbook is read and written from Java: it reads the inputs, computes the metrics, builds the journal rows, and writes them back into the workbook: Java PolicyInput policy = policyReader.read(workbook, "Policy Input"); Metrics metrics = deriveMetrics(policy); // plain Java arithmetic List<JournalRow> rows = journalBuilder.build(readJournalRules(workbook), metrics); sheetWriter.replaceTable(workbook, "Journal Entries", rows); revealSheets(workbook, "Journal Entries", "Report Impact", "Journal CSV"); The interesting part is the last line. Sheet visibility is an application decision: outputs appear only after a successful run, so a reviewer can never mistake stale output for fresh output. The reviewer then sees everything in one place — assumptions, rules, generated journals, report impact — in the same grid, and the export button produces a file the application has logged and versioned. deriveMetrics itself is deliberately simple — a handful of multiplications and subtractions. In a real system it may be far more complex, or it may even delegate back to formulas in the workbook. The architecture doesn't change: inputs go into agreed regions, outputs come from agreed regions, and Java owns the trigger. The Part Everyone Skips: The Workbook Is Now an API The moment Java code depends on a sheet named ETL Rule with a header called Allowed Values, the workbook has stopped being a document. It has become an interface — and interfaces break when they're changed casually, without review. The fix is to make the contract explicit and test it. Distinguish two kinds of change: Value changes – a new allowed currency, an adjusted threshold, a reviewed formula edit. These live inside the contract. Finance can make them without touching Java.Structural changes – renaming a sheet, deleting a header, moving an output table three columns right. These are API changes and should be reviewed like one. Then write this test: Java @Test void templateSatisfiesReportingContract() { Workbook wb = engine.load("reporting-template.xlsx"); assertSheetExists(wb, "Input CSV", "ETL Rule", "Journal Entries"); assertHeaders(wb, "ETL Rule", "Field", "Check", "Allowed Values"); } It looks almost too simple to matter, but most real-world workbook integration failures are exactly this mundane — a renamed sheet or a deleted header, discovered the night before a regulatory filing is due. Catching it in CI, before any template goes live, is what makes the difference. Finally, log runs, not just files: template version, who ran it, validation status, output row counts, a hash of the inputs. When someone asks “why does this quarter's filing look wrong?”, you answer from the run log instead of from archaeology on a shared drive. For compliance teams, these controls turn the workbook from an informal file into evidence the organization can explain. A reviewer can trace which template version produced a number, which source data was used, who ran the process, whether validation passed, and which output was exported. If a template structure changes, the contract test shows whether the workbook still satisfies the application’s required sheets and headers before it reaches production. In other words, the system does not just calculate results; it records the evidence needed to defend how those results were produced. When to Consider a Simpler Approach This approach pays off when the workbook is a genuine shared language between domain experts and developers — something both sides actually read, edit, and rely on. If the rules rarely or never need to change, and nobody inspects formulas, plain Java is simpler to test and operate. And if the workbook is really just a transfer format between systems, a straightforward import/export covers it. Takeaways The compliance-reporting spreadsheet doesn't have to be rewritten or worked around. Put it inside the application and split ownership along a clear line: The workbook owns what users must see and maintain: source rows, rule tables, assumptions, reviewable outputs.The application owns execution: validation, generation, sheet visibility, permissions, logging, export.The contract between them — named sheets, headers, regions — is documented, tested in CI, and changed only with review. Do that, and the workbook stops being an unversioned file nobody can fully account for. It becomes a governed part of the application — the place where domain experts and the system finally agree on the numbers.

By Hawk Chen DZone Core CORE
Differential Flamegraphs in Java in Jeffrey Microscope
Differential Flamegraphs in Java in Jeffrey Microscope

In the first article, we got started with Jeffrey Microscope and learned to read a single flamegraph — the timeseries, search, tooltips, and the allocation and wall-clock variants. This time we build directly on that foundation and tackle one of Jeffrey's most powerful features for real-world performance work: the differential flamegraph, which compares two recordings and shows you precisely what changed between them. A single flamegraph tells you where your application spends its time. But the questions that matter most in practice are comparative: Did my optimization actually help?What did this refactor make slower?Where did the extra allocations come from? Staring at two flamegraphs side by side and trying to spot the difference by eye is slow and error-prone — the graphs are large, and the interesting change is often a few frames buried deep in the stack. Jeffrey Microscope's differential flamegraph solves this by overlaying two recordings into a single graph and coloring every frame by how it changed: Red – where the primary profile spends more than the baseline (a regression).Green – where it spends less (an improvement).Deeper shades – brand-new and fully-removed frames, called out distinctly. In this article, we'll take the two recordings from the previous post — the optimized direct serialization path and the garbage-heavy DOM path — set one as a secondary profile, and let the differential view pinpoint exactly which methods account for the difference. We start exactly where the first article left off. Open the optimized recording, jeffrey-persons-direct-serde-cpu.jfr.lz4, and head to the Visualization tab — this is our primary profile, the same CPU flamegraph we explored last time. On its own, it shows where the direct serialization path spends its time, but to turn it into a comparison we need a second recording to diff it against. That's what the Secondary Profile slot in the top bar is for — currently marked NOT SET. In the next step we'll point it at the DOM-based recording and unlock the Differential view in the sidebar. Supported Events Types With the secondary set, the Differential page mirrors the Primary one — a card per event type — but each now shows both sides at once. The value on the left is the baseline (the secondary profile), the value on the right is the primary, and the badge is the relative change from one to the other: a red +N% means the primary has more of that event than the baseline (grew), a green −N% means it has less (shrank). This lets you gauge the overall shift before opening a single graph — whether the change is a rounding-error wobble or a real regression worth investigating. Jeffrey supports differential flamegraphs for every sample-based event it can render normally: Execution Samples – total CPU work. More samples means more time spent on-CPU (37.3K → 39.7K, +6.4% here).Wall-Clock Samples – elapsed time including waiting and blocking, which can move independently of CPU (5.0M → 4.4M, −12.4%).Allocation Samples – memory pressure; switch Use Total Allocation to compare bytes rather than sample count and see the true allocation cost (27.47 GiB → 30.45 GiB, +10.9%).CPU-Time Samples and Method Traces – empty here, but diff identically when the recordings contain them. Each of these numbers is just the headline; the flamegraph below breaks the same delta down frame by frame, so you can see which methods drove it. Click View Flamegraph on the Execution Samples card to open the differential CPU view. Reading the Differential Flamegraph Opening the differential view feels familiar — same timeseries, search, and tooltip as a normal flamegraph — but everything now encodes two profiles at once: The summary bar at the top reports the totals side by side: baseline 35,472 vs primary 39,668, a net +4,196 (+11.83%) flagged as REGRESSED. That's the headline — the primary run did more on-CPU work overall.The timeseries overlays both recordings as two lines — Primary in blue, Secondary (baseline) in red — so you can see where in time the profiles diverge, not just that they differ.The flamegraph colors encode the per-frame change: pale pink/green for frames that shifted a little, and saturated deep red/deep green for frames that exist in only one profile — brand-new work versus work that disappeared entirely. The payoff is in the last two screenshots. Because the optimized and unoptimized paths run through differently-named classes, the diff renders them as a matched pair: the deep-red EfficientPersonService.getNPersons subtree (new in the primary) sitting right next to the deep-green InefficientPersonService subtree (gone from the primary). You're literally seeing the code swap, top to bottom. And hovering a shared frame quantifies it precisely — the tooltip on PersonController.getNPersons shows baseline 854 → primary 525, an IMPROVED −329 (−38.52%) for that endpoint's own path. The differential CPU flamegraph overlays both recordings: the timeseries plots the primary (blue) against the secondary baseline (red), and the summary bar reports baseline 35,472 → primary 39,668, a net +4,196 (+11.83%) marked REGRESSED. The merged flamegraph colors every frame by its change. The shared Tomcat, Coyote, and Spring layers stay mostly pale pink — small shifts — while the summary bar keeps the overall +11.83% delta in view. The flamegraph also captures the JVM's own threads, not just your request path — the CompileBroker / C2Compiler stacks on the left are JIT compilation, and garbage-collection activity shows up the same way. Comparing them across the two recordings tells you whether either run triggered extra spikes in JIT or GC work, a common hidden cost when one version allocates more or churns more code. Deeper into the stack, the two implementations separate out: saturated red columns mark work that is new in the primary profile, while the deep-green columns are paths that existed only in the baseline and disappear in the primary. The optimized EfficientPersonService path (red, added) sits beside the removed InefficientPersonService path (green). Hovering the shared PersonController.getNPersons frame quantifies the change exactly: baseline 854 → primary 525, an IMPROVED −329 (−38.52%). Summary From here, try the same workflow on the Wall-Clock and Allocation differential flamegraphs — the steps are identical, and each reveals a different dimension of the change: time spent waiting, and bytes allocated. Thank you for reading! To go deeper, visit the Jeffrey pages, or reach out to me directly on LinkedIn — I'd love to hear your feedback. And stay tuned: in the next article, we'll step away from flamegraphs and explore one of Jeffrey's JVM Internals views to dig into what the runtime does under the hood.

By Petr Bouda DZone Core CORE
Jeffrey Microscope for Generating Flame Graphs in Java
Jeffrey Microscope for Generating Flame Graphs in Java

Java Flight Recorder (JFR) captures an enormous amount of detail about what your application is doing — but raw JFR files are only as useful as the tools you have to explore them. Jeffrey is an open-source JFR analyzer that specializes in turning JFR events into interactive visualizations, and Jeffrey Microscope is its standalone, single-user deployment: a self-contained application that lets you import recordings and dig into flamegraphs, timeseries, and other views right in your browser. Getting started takes a minute: Standalone JAR – download the latest microscope.jar from the GitHub releases page and start it with java -jar microscope.jar (Java 25 or newer).Docker – skip the setup entirely with docker run -it --network host petrbouda/microscope.Sample recordings – if you want to explore the tool before profiling your own application, the petrbouda/microscope-examples image ships with sample recordings preloaded (docker run -it --network host petrbouda/microscope-examples). In this article, we'll use Jeffrey Microscope to analyze JFR flamegraphs and walk through how they help you find where your application actually spends its time. Let's set up a hands-on environment. Download the latest microscope.jar from the GitHub releases page and launch it (Java 25 or newer): Shell java -jar microscope.jar Open it in your browser, then grab some recordings to analyze — Jeffrey maintains a companion repository of real JFR recordings captured from various serialization and profiling scenarios: Shell git clone https://github.com/petrbouda/jeffrey-recordings The files ship as compressed .jfr.lz4, which Jeffrey Microscope reads natively. Drag one onto the Drop Recordings zone on the dashboard — the upload starts automatically, and within a few seconds you have a profile ready to explore. For this walkthrough, we'll focus on two recordings that profile the same piece of code — an HTTP endpoint that serializes and deserializes JSON — with one deliberate difference between them: jeffrey-persons-direct-serde-cpu.jfr.lz4 – the optimized path. JSON is serialized directly to and from Java objects, with additional caching in place.jeffrey-persons-dom-serde-cpu.jfr.lz4 – the unoptimized path. JSON is routed through a DOM representation (JsonNode) before being converted to Java objects, intentionally creating extra garbage along the way. Because both recordings exercise the same endpoint under the same workload, they make an ideal before-and-after pair for generating flamegraphs and differential graphs, as we show later. Exploring the Primary Flamegraphs Let's start with the optimized recording. Click jeffrey-persons-direct-serde-cpu.jfr.lz4 to open its profile, then head to the Visualization tab and select Primary under Flamegraphs in the sidebar. Jeffrey inspects the recording and presents a card for every flamegraphable event type it found — each ready to render on its own: Execution Samples (jdk.ExecutionSample) – CPU profiling via perf_events, the most relevant card for a CPU profile like this one.Wall-Clock Samples (profiler.WallClockSample) – wall-clock time, including waiting.Allocation Samples (jdk.ObjectAllocationInNewTLAB) – memory allocation, weighted by object count or total bytes.Java Monitor Blocked, Java Thread Park, Java Monitor Wait – lock-contention and thread-parking events. Each card shows the event type, its source (Async-Profiler or the JDK), the sample count, and a few rendering options — for example, Use Thread-mode to split the graph by thread, or Use Total Allocation to weight the allocation flamegraph by bytes rather than sample count. Click View Flamegraph on the Execution Samples card to see where the CPU time goes. Timeseries Above the flamegraph, Jeffrey plots the selected event across the recording's timeline, so you can see how activity changes over the run — warm-up, steady state, and spikes all stand out. Drag the handles on the range selector below to narrow the window, and the flamegraph rebuilds from only the samples in that interval. Flamegraph Each box is a stack frame, its width proportional to the samples that captured it, stacking upward toward the methods running on-CPU. Wide boxes are where time goes. Read top to bottom to follow the full call path from entry point down into your own code. Click any frame to zoom into that subtree. Search The search box highlights every frame matching your query and reports what share of the profile those matches account for — a fast way to answer "how much time is really in my code?" and to locate a method however deep it sits. The Frame Tooltip Hovering a frame shows far more than a sample count: total vs self samples (time through the frame vs directly in it), its bytecode index and source line, and a compilation breakdown — JIT-compiled, C1-compiled, or inlined — revealing how the method was actually executed. Open in IDE, and View Source jump straight to the code, once Microscope is paired with the Jeffrey IntelliJ plugin. Copy for AI The Copy for AI button exports the current view — stacks, weights, and hot paths — as a compact Markdown summary, copied to your clipboard or downloaded as .md. Paste it into e.g. Claude Code and let the AI optimize your code based on runtime profiles from flamegraphs. Other Flamegraphs Everything above applies to more than just CPU. Back on the Primary page, you can open the Allocation and Wall-Clock flamegraphs the same way — same navigation, search, tooltip, and range selector — but each answers a different question: Wall-Clock – where wall-clock time is spent, including waiting, rather than just on-CPU work.Allocation – where memory is allocated. Two rendering options are worth trying: Use Thread-mode – splits the graph by thread, showing per-thread call trees instead of one merged view. Handy when a single thread dominates or misbehaves. Use Total Allocation – switches the allocation graph from sample count to weight: each frame is sized by the number of bytes allocated rather than how many samples hit it, so a rarely-sampled path that allocates large objects shows up at its true cost. Weighting by the event's own measure instead of sample count often paints a very different — and more actionable — picture. Summary In this article, we set up Jeffrey Microscope and walked through reading a flamegraph — the timeseries and range selector, search, the frame tooltip, the Copy for AI export, and the allocation and wall-clock variants. That's already enough to find where an application spends its time and to start optimizing with confidence. Thank you for reading! To go deeper, visit the Jeffrey pages, or reach out to me directly on LinkedIn — I'd love to hear your feedback. And stay tuned: in the next article, we'll put these two recordings side by side and show how Jeffrey's Differential flamegraph pinpoints exactly what changed between the optimized and unoptimized code.

By Petr Bouda DZone Core CORE

Monthly Top Java Experts

expert thumbnail

Muhammed Harris Kodavath

Senior Technical Manager,
Baptist Health South Florida

With more than 21 years of experience in designing, analyzing, developing, and managing mobile, web, and enterprise client–server applications, I have worked extensively on large-scale, database-driven systems and distributed platforms. My background includes deep hands-on experience building J2EE-based solutions and modern cloud-native applications, along with mobile applications developed using Flutter. I have practical experience working with cloud platforms and serverless architectures, including AWS Lambda and Google Cloud Platform (GCP), and have been actively exploring AI-driven development using tools and models such as Gemini. My focus has consistently been on building scalable, secure, and high-performing systems that align technology delivery with business outcomes. For the past 5 years managing Mobile Application developed in Flutter.
expert thumbnail

Rahul Tewari

Software Engineer Expert,
UPMC

expert thumbnail

Otavio Santana

Award-winning Software Engineer and Architect,
OS Expert

Otavio is an award-winning software engineer and architect passionate about empowering other engineers with open-source best practices to build highly scalable and efficient software. He is a renowned contributor to the Java and open-source ecosystems and has received numerous awards and accolades for his work. Otavio's interests include history, economy, travel, and fluency in multiple languages, all seasoned with a great sense of humor.
expert thumbnail

Daniel Oh

Senior Principal Developer Advocate,
IBM

Java Champion, CNCF Ambassador & TAG DevEX Co-Chair, AAIF Ambassador, Microsoft MVP, Developer Advocate, Technical Marketing, Keynote Speaker, Published Author

The Latest Java Topics

article thumbnail
HTTP QUERY Method Explained: RFC 10008, Ecosystem Adoption, and a Quarkus Implementation
RFC 10008's new QUERY method is safe and cacheable like GET but carries content like POST. This article explains the spec and runs it on Quarkus today.
August 6, 2026
by Hüseyin Akdoğan DZone Core CORE
· 322 Views
article thumbnail
I Built a Java Version Manager by Fixing Other Tools' Open Bugs
There is no point in shipping another Java Version Manager unless it is best in class, so I mined the test suites and bug trackers of SDKMAN, jenv, mise, volta, and asdf.
August 4, 2026
by David Lerner
· 1,601 Views · 1 Like
article thumbnail
Rethinking Java Design Patterns: From OOP to FP
This article aims to adopt a more systematic and practical approach to combining Java object-oriented principles in a functional style.
August 4, 2026
by Nicolas Duminil DZone Core CORE
· 3,948 Views · 5 Likes
article thumbnail
Arrays in Java
Arrays in Java are fundamental data structures used to store elements of the same type sequentially in memory. They provide a convenient way to manage collections of data where each element is accessed by its index.
July 31, 2026
by Vincenzo Marrazzo
· 1,253 Views · 1 Like
article thumbnail
Building a Config-Driven SOAP/REST Integration Layer: One Service, Many Protocols
Learn how to build protocol-agnostic middleware that supports SOAP and REST integrations with configurable authentication and customer-specific transformations.
July 30, 2026
by Balaji Venkatasubramaniyar
· 1,351 Views
article thumbnail
This One Spring Data JPA Pattern Cleaned Up to 3 Years of Repository Debt
Stop adding repository methods every time a filter changes. JPA Specifications let you compose queries cleanly at runtime.
July 29, 2026
by Ramesh Bellamkonda
· 2,262 Views · 1 Like
article thumbnail
The Java Story: The Official Documentary Is Here
This traces Java’s evolution from Oak to a global software platform, revealing its impact on open source, standards, engineering, and the community behind it.
July 29, 2026
by Otavio Santana DZone Core CORE
· 1,751 Views · 1 Like
article thumbnail
How to Build Living AI Coding Assistants With Quarkus Agent MCP
Supercharge local development with the standalone Quarkus Agent MCP server, allowing AI assistants to run, monitor, and debug Java applications.
July 23, 2026
by Daniel Oh DZone Core CORE
· 3,271 Views · 3 Likes
article thumbnail
Stop Writing If-Else Spaghetti: Architecting Cleaner Java with the Strategy Pattern
Stop writing messy nested conditionals. Learn how to combine Java Enums, Functional Interfaces, and Spring to build a scalable Strategy Pattern.
July 23, 2026
by Rahul Tewari
· 6,498 Views · 3 Likes
article thumbnail
Hardening MCP Gateways: Mitigating July 28 Security Risks in Java Applications
The latest MCP updates introduce security risks like protocol confusion. Quarkus mitigates these vectors using strict request filtering and enterprise security layers.
July 21, 2026
by Daniel Oh DZone Core CORE
· 3,036 Views
article thumbnail
Mitigating Cache Stampedes in Dynamic API Translation Using Java 21 Virtual Threads
Building a dynamic API translation proxy that leverages Java 21 Virtual Threads and Redisson distributed locking to safely execute AI-driven schema mapping.
July 17, 2026
by Aniruddha Chatterjee
· 3,436 Views · 3 Likes
article thumbnail
AGENTS.md Makes Your Java Codebase AI-Agent Ready
A standardized instruction layer for enabling AI agents to accurately navigate, build, and test applications by enforcing clear architectural and operational constraints.
July 17, 2026
by Daniel Oh DZone Core CORE
· 3,711 Views · 3 Likes
article thumbnail
Going Stateless: Scaling MCP Servers to Cloud-Native Java and HTTP
The Model Context Protocol has evolved to be entirely stateless over HTTP, removing complex session bottlenecks. Pairing this update with cloud-native Java, Quarkus!
July 16, 2026
by Daniel Oh DZone Core CORE
· 4,630 Views · 2 Likes
article thumbnail
Compliance Reporting Without Losing the Spreadsheet or the Control
Keep the spreadsheet UI for domain experts, but move validation, execution, logging, and export into a governed Java application.
July 14, 2026
by Hawk Chen DZone Core CORE
· 3,402 Views · 2 Likes
article thumbnail
Differential Flamegraphs in Java in Jeffrey Microscope
How to set up a secondary profile and pinpoint the precise frames responsible for a performance change between two JFR recordings.
July 14, 2026
by Petr Bouda DZone Core CORE
· 1,986 Views · 1 Like
article thumbnail
Jeffrey Microscope for Generating Flame Graphs in Java
Jeffrey Microscope is a deep analyzer for JFR recordings, with a particular focus on flamegraphs generated from stacktrace-based events
July 13, 2026
by Petr Bouda DZone Core CORE
· 2,482 Views · 1 Like
article thumbnail
Your Codename One App, Now A Native Mac App
Learn how Codename One 7.0.250 brings native macOS app builds, desktop integration, native menus, and improved desktop UX.
July 10, 2026
by Shai Almog DZone Core CORE
· 2,388 Views · 1 Like
article thumbnail
Exploring A Few Java 25 Language Enhancements
Brief and with practical code examples that raise developers' curiosity and interest in exploring these language enhancements in detail.
July 10, 2026
by Horatiu Dan DZone Core CORE
· 2,529 Views · 1 Like
article thumbnail
HTTP QUERY in Java: The Missing Method for Complex REST API Searches
HTTP QUERY gives Java REST APIs a cleaner way to handle complex searches with request bodies, avoiding long URLs and POST misuse while keeping reads explicit.
July 6, 2026
by Otavio Santana DZone Core CORE
· 2,183 Views
article thumbnail
OBO SSO in Java Applications: Securely Calling Downstream APIs on Behalf of a User
OBO (On-Behalf-Of) allows a Java API to securely call downstream services using the authenticated user's identity instead of the application's identity.
July 3, 2026
by Muhammed Harris Kodavath
· 1,764 Views · 3 Likes
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • ...
  • Next
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook
×