Posts Tagged ‘Java’
[MunchenJUG] Navigating the JVM Ecosystem: A Safari Through Distributions (16/Sep/2024)
Lecturer
Gerrit Grunwald is a highly regarded software engineer and advocate with four decades of experience in the technology sector. He is a prominent figure in the Java community, recognized as a Java Champion and a JavaOne Rockstar. Gerrit is deeply committed to open-source software, having contributed to and led numerous projects such as JFXtras, TilesFX, Medusa, and JDKMon. He founded and leads the Java User Group Münster and is a frequent speaker at international conferences. Currently, Gerrit serves as a Developer Advocate at Azul.
Abstract
This article provides an analytical overview of the modern Java Virtual Machine (JVM) landscape, distinguishing between the OpenJDK project and its various commercial and community distributions. It evaluates the shift in Java’s release cadence and the implications for long-term support (LTS) in corporate environments. A significant portion of the analysis is dedicated to the optimization of Java runtimes through modularity and the jlink tool, demonstrating how developers can significantly reduce deployment sizes and enhance security. Finally, the article categorizes the plethora of available JDK distributions—from major cloud providers like Amazon and Alibaba to specialized runtimes like GraalVM—offering a guide for selecting the appropriate distribution based on specific use cases.
The Distinction Between OpenJDK and Distributions
A fundamental misunderstanding in the Java community is the conflation of “OpenJDK” with the software installed on a user’s machine. OpenJDK is not a downloadable product but rather the open-source project hosted on GitHub that contains the source code for the Java Platform, Standard Edition (Java SE). What developers actually utilize are “builds” or “distributions” of this source code.
The OpenJDK ecosystem is characterized by its collaborative nature, with significant contributions from tech giants such as Oracle, Amazon, ARM, Google, Intel, and IBM. This multi-corporate backing ensures the longevity and stability of the platform, preventing it from becoming a “one-man show”. Since moving to GitHub with JDK 16, the transparency and accessibility of the source code have further improved, allowing for faster build times and broader community involvement.
Release Cadence and Support Models
The evolution of Java’s release model marks a critical transition from multi-year development cycles to a predictable six-month cadence. Historically, long gaps between releases (such as the five years between JDK 6 and JDK 7) led to massive, overwhelming updates that were difficult for organizations to adopt.
The current model classifies releases into two categories:
- Feature Releases: Released every six months, these versions typically receive support for only half a year.
- Long-Term Support (LTS) Releases: These versions are designated for extended support, often spanning a decade or more, providing the stability required by enterprise applications.
This dual-track approach allows the language to innovate rapidly through feature releases while providing a safe harbor for production environments on LTS versions.
Efficiency through Modularity: The jlink Revolution
One of the most underutilized innovations introduced in JDK 9 is the modularization of the Java runtime. By breaking the monolithic JDK into 69 distinct modules, Oracle enabled developers to create custom, stripped-down runtimes tailored to specific applications.
The tool jlink allows for the creation of a custom Java Runtime Environment (JRE) containing only the modules necessary for a particular application. The impact on deployment size is profound:
- A full JDK 21 installation requires approximately 340 MB.
- A standard JRE for the same version takes about 150 MB.
- A
jlink-optimized runtime for a simple application (like a push notification server) can be as small as 48 MB.
echo Example of using jdeps to find required modules
jdeps --ignore-missing-deps --print-module-deps MyProject.jar
echo Example of using jlink to create a custom runtime
jlink --add-modules java.base,java.logging --output custom-runtime
Beyond storage savings, modular runtimes enhance security by reducing the attack surface. If a vulnerability exists in a module that has been excluded from the custom runtime (such as the desktop module in a server-side application), the application remains unaffected.
Mapping the Distribution Jungle
The JVM landscape is populated by numerous distributions, each offering different levels of support, licensing, and platform optimizations.
Community and Vendor Builds
- Eclipse Temurin (formerly AdoptOpenJDK): A widely used community build that is TCK (Technology Compatibility Kit) compliant.
- Amazon Corretto: A no-cost, multiplatform distribution used internally by Amazon for its AWS services.
- Azul Zulu: A TCK-compliant distribution offering broad platform support.
- Oracle OpenJDK: The free, GPL-licensed build provided by Oracle.
Region-Specific and Specialized Distributions
In the Asian market, distributions like Alibaba’s Dragonwell, Huawei’s Bi Sheng, and Tencent’s Kona are dominant. These often include specific optimizations for the cloud infrastructures of their respective parent companies.
Advanced Runtimes: GraalVM and Beyond
GraalVM represents a specialized branch of the JVM ecosystem, offering high-performance polyglot capabilities and “Native Image” compilation. Native images allow Java applications to start in milliseconds by compiling them into platform-specific executables, though this comes at the cost of peak performance and longer build times compared to the standard JIT (Just-In-Time) compilation used by the HotSpot JVM.
Conclusion: Strategy for Selection
Choosing the right JVM distribution is a strategic decision based on support requirements, cost, and technical constraints. For most production environments, sticking to an LTS version from a reputable vendor (like Azul, Amazon, or the Eclipse Foundation) ensures stability. Meanwhile, developers should leverage modern tools like jlink to ensure their deployments remain lean and secure, regardless of the distribution chosen.
Links:
Understanding SecureRandom in Modern Java: new SecureRandom() vs SecureRandom.getInstanceStrong()
For many Java developers,
generating cryptographically secure random values appears straightforward:
SecureRandom random = new SecureRandom();
or perhaps:
SecureRandom random = SecureRandom.getInstanceStrong();
Both approaches produce a SecureRandom instance. Both are
designed for cryptographic use cases. Both are significantly more secure than java.util.Random.
Yet beneath these seemingly simple APIs lies a surprisingly
complex interaction between the JVM, security providers, operating system entropy sources, and cryptographic standards.
Understanding these details is important because
the choice of random number generator can impact:
- Application startup time
- Cryptographic strength
- Portability across platforms
- Container and cloud deployment behavior
- Compliance requirements
- Operational reliability
This article examines how Java’s secure random number generation works, what differentiates new SecureRandom() from
SecureRandom.getInstanceStrong(), and which approach should be preferred in modern enterprise environments.
Why Cryptographically Secure Randomness
Matters
Modern applications rely on secure randomness far more often than many developers realize.
Common examples include:
- Session identifiers
- JWT signing keys
- Password reset tokens
- OAuth state parameters
- CSRF protection
- TLS handshakes
- Key generation
- Digital signatures
- Encryption initialization vectors
- Nonces
The fundamental requirement is unpredictability.
An attacker capable of predicting future outputs of a random number generator can often compromise the entire
security model of an application.
This is why Java provides SecureRandom, a cryptographically secure pseudo-random number generator (CSPRNG), specifically
designed to withstand prediction attacks.
What Happens When You Call new SecureRandom()?
Consider the following code:
SecureRandom random = new SecureRandom();
Most developers assume this directly instantiates a specific implementation.
In
reality, the JVM delegates the selection to the Java Security Provider architecture.
At runtime, Java:
- Inspects the configured security providers
- Searches for available
SecureRandomimplementations - Selects the preferred implementation
- Instantiates and seeds it
The resulting algorithm depends on several factors:
- JDK version
- Operating system
- Security provider configuration
- Security policy
On contemporary JDKs (17, 21 and beyond), the implementation is frequently one of:
DRBG
or
NativePRNG
depending on platform and configuration.
You can verify the actual implementation:
SecureRandom random = new SecureRandom(); System.out.println(random.getAlgorithm()); System.out.println(random.getProvider());
Typical output:
DRBG SUN
or:
NativePRNG SUN
The important observation is that new SecureRandom() does not imply a particular algorithm. It requests the JVM’s default
secure random implementation.
Enter SecureRandom.getInstanceStrong()
Java 8 introduced a new API:
SecureRandom random =SecureRandom.getInstanceStrong();
This method has a different objective.
Rather than selecting the
default implementation, it requests the strongest secure random generator configured on the platform.
Internally, Java consults the following security property:
securerandom.strongAlgorithms
located in:
$JAVA_HOME/conf/security/java.security
Typical values may look like:
securerandom.strongAlgorithms= NativePRNGBlocking:SUN, DRBG:SUN
Java attempts to instantiate the first suitable candidate.
Unlike new, the resulting implementation is explicitly influenced by the platform’s definition of “strong”.
SecureRandom()
Historical Context: /dev/random
versus /dev/urandom
To understand why this distinction exists, we need to revisit Linux entropy management.
Historically, Linux exposed two primary
entropy interfaces:
/dev/random
and
/dev/urandom
/dev/random
- Uses entropy collected from environmental noise
- May block when entropy is considered insufficient
- Traditionally regarded as the most conservative source
/dev/urandom
- Non-blocking
- Uses a cryptographically secure internal PRNG
- Continues producing output even when entropy pools are depleted
For many years, security guidance often favored /dev/random for highly sensitive operations.
Consequently, some JVM implementations mapped “strong”
random generation to entropy sources capable of blocking.
This design decision eventually led to one of the most infamous operational issues in Java security.
The
Startup Hang Problem
Many developers encountered situations similar to the following:
SecureRandom random =SecureRandom.getInstanceStrong();
Application startup would appear frozen:
Starting Spring Boot application...
And then nothing.
The process was waiting for entropy.
This behavior was especially common in:
- Virtual machines
- Cloud environments
- Docker containers
- Kubernetes clusters
- Minimal Linux distributions
The issue was not Java itself. The underlying operating system simply refused to provide additional entropy at that moment.
How Modern Linux Changed the
Equation
Modern Linux kernels use the getrandom() system call and maintain cryptographically strong entropy pools that become secure shortly after system
initialization.
Today:
- Linux entropy management is significantly improved
- OpenJDK implementations have evolved accordingly
- Container platforms inherit entropy from mature host systems
- Blocking behavior is far less common
As a result, the historical distinction between /dev/random and /dev/urandom has become much less relevant for most production workloads.
The Rise of DRBG
Since JDK 9, Java includes support for NIST SP 800-90A Deterministic Random Bit Generators (DRBGs).
SecureRandom random =SecureRandom.getInstance("DRBG");
DRBG implementations provide:
- Well-defined cryptographic properties
- Explicit security strength
- Standardized behavior
- Alignment with modern compliance frameworks
What Should You Use in Spring Boot on EKS?
Consider a typical modern deployment:
Spring Boot↓ Container↓ Amazon EKS↓ EC2↓ Linux Kernel
For this environment, the recommended choice is usually:
private static final SecureRandom RANDOM =new SecureRandom();
or, when explicit algorithm selection is desired:
SecureRandom.getInstance("DRBG");
Using SecureRandom.getInstanceStrong() is generally unnecessary unless your
organization has specific compliance or regulatory requirements demanding the strongest available implementation.
Conclusion
The distinction between new and
SecureRandom()SecureRandom.getInstanceStrong() reflects the evolution of both operating systems and the JVM.
For most enterprise Java workloads,
including Spring Boot applications deployed on Kubernetes, EKS, ECS, OpenShift, or traditional Linux servers, new SecureRandom() provides an excellent balance of
security, performance, portability, and operational reliability.
When stronger guarantees or compliance requirements exist, DRBG or getInstanceStrong() may
be appropriate. However, these should be deliberate architectural choices rather than defaults applied indiscriminately.
In modern Java platforms, secure randomness is no
longer primarily about finding the strongest entropy source. It is about selecting a solution that delivers robust cryptographic guarantees while remaining operationally
predictable at scale.
[VoxxedDaysBucharest2026] Building a Sarcastic, Agentic Pair Programmer: Alexander Chatzizacharias on Crafting Playful LLM Workflows
Lecturer
Alexander Chatzizacharias is a software engineer at JDriven, a specialized consultancy in the Netherlands focused on JVM technologies and modern software development practices. With a unique background blending Dutch and Greek influences and a keen interest in game studies, Alexander brings creativity and playful thinking to technical challenges. He frequently speaks on topics including Java, Spring Boot, AI applications, and innovative development workflows.
Abstract
As mainstream AI coding assistants converge toward similar polished but somewhat generic experiences, Alexander Chatzizacharias demonstrates how to build a highly personalized, characterful AI pair programmer named “Pip.” Inspired by interactions with a sarcastic colleague named Ricardo, Pip incorporates personality through vectorized Slack history, utilizes Spring Boot and Kotlin, runs entirely locally with Qwen models via Ollama, and employs sophisticated workflows, multi-vector RAG, and the Model Context Protocol (MCP) to create delightful and productive assistance while addressing challenges like non-determinism and model drift.
The Homogenization of AI Assistants and the Quest for Personality
Alexander observes that leading AI coding tools have converged on remarkably similar chat-based interfaces and interaction patterns, largely influenced by OpenAI’s design choices. While incremental improvements continue, the overall experience feels increasingly uniform. This observation inspired the creation of Pip — an intentionally quirky, sarcastic AI pair programmer that injects personality drawn from real colleague interactions.
By processing Slack conversation history into vector embeddings stored in Qdrant, Pip can retrieve and emulate Ricardo’s characteristic sarcastic tone, witty retorts, and playful threats (such as threatening to delete poorly written code). This transforms the assistant from a neutral tool into a more engaging, human-like collaborator that questions unclear requirements, offers humorous feedback, and makes the development process more enjoyable.
Technical Architecture: Workflows, Agents, and Local Execution
Pip is implemented as a Spring Boot application written in Kotlin, with an IntelliJ IDEA plugin providing the frontend interface. Everything runs locally to maintain privacy and control: Qwen 3.5 models served through Ollama handle the language tasks.
Rather than pursuing fully autonomous agents, Alexander favors structured workflows that provide greater determinism and reliability — attributes particularly valued in enterprise environments. A categorization agent, functioning as an LLM-as-Judge, routes incoming queries to appropriate specialized handlers. Each handler uses carefully crafted system prompts derived from Slack history to consistently embody the desired personality traits.
The architecture incorporates multiple specialized agents for response generation, sophisticated RAG pipelines leveraging both dense and sparse vector representations with ColBERT reranking for improved retrieval quality, and integration with the Model Context Protocol (MCP) for tool usage such as playing music or generating memes when appropriate.
RAG, Tools, and the Challenges of Non-Determinism
Retrieval-Augmented Generation forms a cornerstone of Pip’s capabilities, dynamically pulling relevant context to overcome the inherent token limitations of even advanced models. Multi-vector search strategies combine semantic understanding with keyword precision for more reliable information retrieval from project documentation, codebases, and conversation history.
Tool integration via MCP enables rich interactions but introduces additional complexity due to the non-deterministic nature of local models. Alexander discusses practical challenges including prompt sensitivity to model updates (“model locking” strategies), the art of prompt engineering which he likens to “vibe checking,” and the necessity of implementing guardrails to maintain appropriate behavior boundaries.
Implications for Future AI Development
Alexander encourages attendees to experiment with building personalized, domain-specific AI assistants using accessible open-source tools. While acknowledging the increasing commercialization of AI, he emphasizes the current window of opportunity for creative, playful implementations that enhance both productivity and developer satisfaction.
Pip serves as an inspiring example of how thoughtful combination of RAG techniques, vector databases, workflow orchestration, and personality injection can create AI tools that feel genuinely collaborative rather than merely functional.
Links:
[MiamiJUG] How Scala Modernized the Java Ecosystem: A Functional Retrospective
Lecturer
Joan Goyeau is a Senior Playback Data Engineer at Netflix, where he specializes in building high-scale distributed systems using functional programming paradigms. He is a prolific contributor to the open-source community, with notable involvement in projects such as the Mill build tool, the Kubernetes Java/Scala Client, Cats, Apache Spark, and Avro4s. Joan’s expertise lies in leveraging the grammatical simplicity of Scala to manage complex data architectures in enterprise environments.
Abstract
This article explores the historical and technical relationship between Scala and Java, framing Scala as a primary driver of innovation for the Java Virtual Machine (JVM). By tracing the lineage of modern Java features—such as generics, lambdas, and records—to their origins in the Pizza and Scala languages, the analysis demonstrates how functional concepts have systematically transitioned into mainstream enterprise development. Furthermore, the study examines the practical advantages of Scala’s minimalist grammar and multi-platform compilation capabilities, specifically within the context of data engineering at scale.
The Evolutionary Lineage: From Pizza to Java 21
The modernization of the Java language is deeply rooted in experiments conducted over two decades ago. In 2001, the “Pizza” language emerged as a superset of Java 1.4, introducing a proof-of-concept for generics, lambdas, and pattern matching. While the Java ecosystem initially only adopted generics, the broader suite of functional features found a permanent home in Scala upon its release in 2004.
In the years following, a “trickle-down” effect occurred where Scala features were progressively integrated into the Java language specification. Java 8 introduced lambdas through the Stream API, Java 14 implemented record classes (conceptually identical to Scala’s case classes), and recent versions have refined pattern matching through switch expressions. This history identifies Scala not just as a standalone language, but as a vanguard for JVM innovation that tests “unknown lands” before they are deemed safe for Java’s more conservative adoption cycle.
Grammatical Simplicity and Language Complexity
A significant technical advantage of Scala is its relatively small formal grammar compared to other modern languages. Analysis of language grammar sizes reveals that while Java and C# have grown in complexity to accommodate specific use cases, Scala maintains a core simplicity that allows for high expressiveness through library definitions rather than language keywords. This design philosophy ensures that the cognitive load remains manageable even as the developer leverages powerful functional features. Notably, newer languages like Kotlin have already surpassed Scala in grammatical size, illustrating the efficiency of Scala’s architectural choices.
Multi-Platform Versatility and Modern Tooling
Beyond its influence on Java, Scala has evolved into a versatile language capable of targeting multiple execution environments. Using the Scala CLI—a streamlined alternative to heavy build tools—developers can manage dependencies and package applications with minimal boilerplate. A single Scala codebase can target:
- The JVM: For traditional high-performance backend services.
- Native: For low-latency binaries that run directly on hardware.
- JavaScript (Scala.js): For front-end web development.
In the context of web development, libraries like Laminar allow developers to build reactive interfaces using type-safe functional structures. By replacing string-heavy HTML/JavaScript interactions with Scala’s rigorous type system, engineers can catch errors at compile-time that would typically manifest as runtime bugs in a traditional JavaScript stack.
Links:
[MunchenJUG] Evolution of Static Analysis: The Journey to PMD 7 (7/Oct/2024)
Lecturer
Andreas Dangel is a distinguished software engineer with extensive expertise in Java, Spring, SQL, and agile methodologies. With a professional career spanning several decades, he has significantly contributed to the IoT consumer electronics industry. Andreas has been a pivotal figure in the open-source community, serving as a maintainer of PMD since 2012 and a committer at the Apache Software Foundation for the Maven project. Currently based in Munich, he continues his professional endeavors at MicroDoc.
Abstract
This article explores the comprehensive transformation of PMD, a leading multi-language static code analyzer, through its significant transition to version 7. It examines the fundamental principles of PMD—including its rule-based architecture and copy-paste detection—while detailing the modernization of its core engine to support evolving language features and improved performance. The analysis highlights the challenges faced during this decade-long development cycle, the shift in architectural paradigms to accommodate complex language parsing, and the strategic roadmap for the future of automated code quality assurance.
The Architecture of Static Analysis: Understanding PMD
PMD serves as a sophisticated static code analyzer designed to identify problematic patterns, common mistakes, and stylistic inconsistencies across various programming languages. Originally established in 2002 as the “Project Mistake Detector,” the tool has evolved into a robust, rule-based ecosystem supporting over ten languages. The system’s utility is grounded in its ability to detect issues that often elude standard compilers, categorized into domains such as error-prone constructs, best practices, code style, and performance.
The engine operates on a rule-based methodology where every detectable problem is governed by a specific rule. PMD offers users more than 400 predefined rules, including 270 specifically for Java. These rules can be customized through two primary methods: writing custom Java classes or utilizing XPath expressions to query the source code’s Abstract Syntax Tree (AST). To facilitate the latter, the PMD ecosystem includes a “Rule Designer” application, allowing developers to visualize code structures and test XPath queries in real-time.
Beyond standard rule checking, PMD includes a specialized Copy-Paste Detector (CPD). Unlike the core engine, which requires deep language parsing, CPD utilizes a different technological approach that allows it to support an even broader range of languages for identifying duplicated code blocks.
Implementation and Integration Strategies
PMD’s versatility is reflected in its diverse integration options within the modern software development lifecycle. Written in Java, the tool can be executed via a simple command-line interface (CLI) or integrated into various build and development environments.
Build Tool Integration
For Java-centric projects, integration via build automation tools is the standard approach:
- Maven: Utilizing the
maven-pmd-plugin, developers can automate code verification and copy-paste detection as part of the build process. - Gradle and Ant: Similar plugins exist to ensure code quality is maintained continuously without manual intervention.
- Quality Gates: By configuring the build to fail upon rule violations, PMD serves as a mandatory quality gate, ensuring that no substandard code reaches the repository.
IDE and CI/CD Ecosystems
To provide immediate feedback, PMD supports major Integrated Development Environments (IDEs) including Eclipse, IntelliJ IDEA, and VS Code. Furthermore, it is deeply integrated into Continuous Integration (CI) services. For instance, Jenkins utilizes specialized plugins to visualize results and track the history of violations across builds, providing insights into whether code quality is improving or deteriorating over time. Modern cloud services and GitHub Apps also leverage PMD to perform automatic code reviews during pull requests, providing comments directly on the affected code blocks.
Innovations in PMD 7: Redesigning the Engine
The transition to PMD 7 represents a fundamental shift in how the tool processes source code. The primary driver for this major release was the need to overcome the limitations of the aging architecture that had been in place for nearly two decades.
The internal redesign focuses on several key areas:
- Parsing Modern Java: As Java’s release cadence accelerated, PMD needed a more flexible way to handle new language features like records, sealed classes, and pattern matching.
- Performance Optimization: The new version introduces architectural changes that improve the speed of analysis, particularly for large-scale projects with hundreds of rules.
- Language Support Expansion: While Java remains a core focus, PMD 7 strengthens its multi-language capabilities, including better support for languages like Salesforce’s Apex.
One of the significant challenges in this journey was maintaining backward compatibility while significantly altering the AST structure. The development team had to balance the introduction of more descriptive node types with the risk of breaking existing custom rules written by the community.
Future Directions and Sustainability
Looking ahead, the PMD project aims to enhance its analysis capabilities by incorporating more data-flow and control-flow sensitivity. This would allow the tool to detect more complex logic errors that require understanding the state of variables across different execution paths.
Sustainability remains a focal point for the project. As an open-source initiative maintained by a small core team of three individuals and occasional contributors, the “Journey to PMD 7” also serves as a case study in open-source lifecycle management. The roadmap includes simplifying the process of writing and maintaining rules to encourage more community participation and ensuring the tool remains relevant in an era of increasing automated development.
Links:
[VoxxedDaysBucharest2026] Breaching LLM-Powered Applications: Brian Vermeer on Security and Privacy Challenges in AI Systems
Lecturer
Brian Vermeer is a Staff Developer Advocate at Snyk, where he focuses on developer security, DevSecOps practices, and emerging risks in modern application architectures. A recognized Java Champion and active community leader who co-leads the Netherlands Java User Group (NLJUG), Brian brings extensive experience in application security, secure coding, and helping organizations build more resilient systems. He frequently speaks at international conferences on topics ranging from traditional web vulnerabilities to the novel attack surfaces introduced by artificial intelligence and large language models.
Abstract
As organizations rapidly integrate Large Language Models into production applications, new categories of security and privacy vulnerabilities emerge alongside familiar web application risks. Brian Vermeer provides a compelling, demonstration-heavy exploration of these challenges through a fictional car rental application called “Really Good Rentals.” He demonstrates practical attack vectors such as prompt injection, RAG poisoning, memory manipulation, and tool abuse, while outlining layered mitigation strategies including input/output guardrails, scoped permissions, human-in-the-loop verification, and architectural defenses essential for building trustworthy LLM-powered systems.
The Allure and Inherent Risks of LLMs in Production Applications
Brian begins by drawing a relatable analogy: just as children enthusiastically misuse new toys in unexpected ways, developers often rush to incorporate powerful new technologies like LLMs without fully appreciating the expanded attack surface they create. While LLMs offer remarkable capabilities for natural language processing, code generation, and intelligent automation, they introduce significant risks when granted access to tools, user data, or execution privileges.
He presents a simplified architecture of a typical LLM-powered application, highlighting key components: user prompts, system instructions, conversation memory, retrieval-augmented generation (RAG) pipelines, and tool-calling mechanisms. Because LLMs are fundamentally stateless, the surrounding application bears responsibility for maintaining context, which creates multiple points where malicious actors can influence behavior through carefully crafted inputs.
Context Poisoning Through RAG and Memory Manipulation
A central demonstration revolves around the “Really Good Rentals” application. Brian shows how a seemingly innocuous file upload feature with inadequate path validation allows attackers to perform directory traversal and overwrite critical documents stored in the vector database, such as terms-of-service files. By injecting a modified cancellation policy containing trigger phrases like “vroom vroom,” the attacker can later invoke this policy through normal chat interactions, tricking the LLM into granting unauthorized credits or violating business rules.
This technique, termed RAG poisoning, illustrates how tainted retrieval sources can persistently influence model behavior across conversations. Similar vulnerabilities arise through traditional injection attacks in search functionality, where SQL injection not only extracts data but also poisons the conversation memory fed to the LLM on subsequent interactions. Brian emphasizes that classic web vulnerabilities gain dramatically amplified impact when they shape the context provided to powerful generative models.
Abusing Permissions, Tool Calling, and Advanced Prompt Injection
Brian demonstrates how overly broad tool permissions create dangerous scenarios. In older models like GPT-3.5, carefully crafted prompts could coerce the LLM into executing arbitrary SQL statements with destructive consequences. Even with more recent, safety-aligned models, insufficient scoping of available tools allows privilege escalation and unauthorized actions.
Advanced prompt injection techniques go beyond simple overrides. Multi-turn attacks gradually extract personally identifiable information by leveraging accumulated conversation memory. When combined with tool calling capabilities, especially with locally hosted models, attackers can trigger hallucinations that inadvertently expose sensitive data during operations such as account creation or data processing.
The presentation underscores that granting LLMs access to powerful tools without rigorous permission boundaries and validation is equivalent to giving untrusted code broad system access.
Mitigation Strategies and Architectural Defenses
Brian outlines a comprehensive defense-in-depth approach spanning multiple layers:
-
Input and Output Guardrails: Deploying dedicated LLM-as-a-Judge mechanisms that evaluate both incoming prompts and generated outputs for malicious content, policy violations, or harmful instructions. These guardrails act as critical safety nets.
-
Limited-Scope and Permission-Aware Tools: Designing tools with granular permissions, explicit user confirmation flows for sensitive operations, and runtime validation of actions against the authenticated user’s privileges.
-
Structured Outputs and Schema Enforcement: Using techniques that force models to produce responses conforming to predefined schemas, significantly reducing the potential for unexpected or harmful outputs.
-
Model Selection and Routing: Strategically routing sensitive operations to private, self-hosted models while reserving more powerful commercial models for less critical tasks.
-
Traditional Security Foundations: Maintaining rigorous input sanitization, dependency updates, secure file handling, and regular security scanning. Brian stresses that foundational web application security remains non-negotiable even in AI-enhanced systems.
Additional considerations include implementing rate limiting to prevent “denial of pocket money” attacks that exhaust token quotas through malicious prompting, as well as comprehensive auditing of all tool invocations and model interactions.
Broader Implications for Secure AI Development
The talk concludes with forward-looking guidance for organizations adopting LLM technologies. Brian encourages treating LLMs as powerful but inherently unpredictable components requiring the same rigorous engineering discipline applied to any critical system. Key principles include careful context management, strict permission boundaries, deterministic fallback mechanisms where possible, and continuous security education for development teams.
By sharing concrete attack demonstrations and corresponding defenses, Brian equips attendees with actionable insights to build more secure, privacy-preserving AI applications while continuing to harness their transformative potential.
Links:
[MiamiJUG] Bridging the Gap: A Java Developer’s Guide to the Go Ecosystem
Lecturer
Vladimir Vivien is a veteran software engineer with over 20 years of experience in the technology industry. A specialist in distributed systems and cloud-native architecture, Vladimir spent the first decade of his career as a dedicated Java developer before transitioning to the Go programming language roughly twelve years ago. He is the author of the authoritative text Learning Go Programming and the creator of the LinkedIn Learning course Programming with Go Modules. Vladimir is a passionate advocate for well-architected solutions and currently focuses on building high-performance systems that leverage Go’s unique concurrency primitives.
Abstract
As the backbone of cloud-native infrastructure, the Go programming language (Golang) has become an essential tool for modern software engineering. This article provides a comparative analysis of Go and Java, designed specifically for practitioners familiar with the Java Virtual Machine (JVM) ecosystem. While both languages share a commitment to static typing and garbage collection, they diverge significantly in their approaches to concurrency, deployment, and error handling. By exploring Go’s syntax, its “share by communicating” philosophy via channels, and its deterministic build system, this study highlights how Go simplifies common programming tasks while maintaining the performance required for large-scale systems like Kubernetes and Docker. The analysis concludes by examining Go’s role in the industry and its strategic advantages for distributed architectures.
The Origins and Industry Adoption of Go
Go was developed at Google to solve large-scale software engineering challenges. It was designed not merely as a language, but as a comprehensive suite of tools to address issues like packaging, supply chain security, and build-time performance. Since its public release in 2009, Go has consistently ranked among the most loved languages by developers.
Go’s dominance is particularly evident in the cloud-native and DevOps sectors. Critical infrastructure tools such as Kubernetes, Docker, Terraform, and Prometheus are all written in Go. This is not coincidental; Go’s ability to compile into a single, static binary with fast startup times and low memory overhead makes it ideal for containerized environments. Vladimir notes that while Java offers “Write Once, Run Anywhere” via the JVM, Go provides “Write Once, Compile Anywhere,” targeting specific architectures with a highly optimized toolchain.
Comparative Architecture: Go vs. Java
For the Java developer, Go introduces several paradigm shifts in how code is structured and executed:
Static Typing and Inference
Both languages utilize strict static type systems. However, Go supports implicit typing through the := short variable declaration operator, allowing the compiler to infer the type based on the assigned value. This provides the brevity of a dynamic language while maintaining the safety of static checks at compile time.
Garbage Collection
Go and Java are both garbage-collected. However, whereas Java provides developers with numerous “knobs” and parameters to tune the JVM’s garbage collector, Go takes a minimalist approach. The Go runtime is designed to deliver sub-millisecond GC pauses with almost no manual configuration, relying on compiler optimizations and escape analysis to manage memory efficiently.
Concurrency: Go-routines and Channels
The most significant departure from Java’s threading model is Go’s approach to concurrency. Instead of heavy OS-level threads, Go uses “go-routines”—lightweight threads managed by the Go runtime that cost only a few kilobytes of memory.
Go’s philosophy of concurrency is summarized as: “Do not communicate by sharing memory; instead, share memory by communicating.” This is achieved through Channels, conduits that allow go-routines to pass data safely without the need for traditional locks or race condition worries.
Example of a basic worker pattern in Go:
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results) // Launch 3 lightweight go-routines
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// Results are popped out as they are processed
}
Explicit Error Handling and Resource Management
Unlike Java, which relies on a hierarchy of Exceptions that bubble up the call stack, Go requires explicit error handling. Functions in Go can return multiple values, and by convention, the last value is often an error type.
Vladimir explains that this “check everything” approach prevents silent failures and forces developers to consider failure states as part of the primary logic flow. Additionally, Go replaces Java’s try-with-resources or finally blocks with the defer keyword, which schedules a function call (like closing a file or network connection) to run immediately before the surrounding function returns.
Conclusion: Where Go Shines
Go’s design choices prioritize simplicity, readability, and performance. It excels in building CLI tools, distributed systems, and high-performance APIs capable of handling thousands of concurrent connections out of the box. For the Java developer, Go offers a streamlined alternative that reduces the complexity of modern cloud-native development without sacrificing the robustness required for enterprise-scale engineering.
Links:
[MiamiJUG] Taming Vulnerabilities and Technical Debt Through Deterministic Refactoring
Lecturer
Kevin Brockhoff is a Director and Consulting Expert at CGI, one of the world’s largest IT and business consulting firms. With decades of experience in the technology industry, Kevin specializes in navigating the complex intersections of cybersecurity, digital transformation, and large-scale enterprise systems. His work at CGI involves helping multinational organizations—spanning sectors such as banking, government, and manufacturing—modernize their legacy infrastructure while maintaining robust security postures. Kevin is a prominent voice in the Miami technology community, frequently sharing insights at the Miami Java User Group (MiamiJUG) regarding automated refactoring and the integration of generative AI in software engineering.
Abstract
As enterprises face an accelerating stream of feature requests and increasingly sophisticated cyber threats, the accumulation of technical debt and security vulnerabilities has become a critical bottleneck. This article examines a deterministic approach to large-scale code remediation using OpenRewrite, an open-source automated refactoring ecosystem. Unlike indeterminate generative AI agents, which can produce inconsistent results and hallucinations, OpenRewrite utilizes Lossless Semantic Trees (LSTs) to ensure predictable, traceable, and scalable code transformations. By combining the creative potential of AI with the reliability of rule-based transformers, organizations can achieve a fourfold increase in productivity for vulnerability remediation. The following analysis explores the methodology of LST-based refactoring, its application across thousands of repositories, and its strategic role in modernizing global IT infrastructure.
The Crisis of Speed and Indeterminacy in Enterprise Software
In the modern software landscape, engineering teams are caught in a perpetual race between delivering new features and mitigating emerging security risks. Kevin emphasizes that speed is the decisive factor in this environment; delays in remediation allow vulnerabilities to proliferate across growing application portfolios. While generative AI agents have been proposed as a solution to this problem, they introduce significant challenges when applied in isolation at an enterprise scale.
The primary issue with relying solely on Large Language Models (LLMs) for code refactoring is their indeterminate nature. Applying an AI agent to the same codebase multiple times may yield different results, and the risk of “hallucinations” necessitates a manual human review of every line of code. Furthermore, current AI tools often struggle with scalability; while they may function effectively on a single repository, managing transformations across 5,000 repositories requires a more structured, traceable mechanism.
OpenRewrite: Deterministic Refactoring via Lossless Semantic Trees
To address the limitations of AI, Kevin advocates for the use of OpenRewrite, a tool sponsored by Moderne that provides a deterministic framework for source code modification. At the heart of OpenRewrite is the Lossless Semantic Tree (LST). While a traditional Abstract Syntax Tree (AST) represents the hierarchical structure of code, the LST incorporates two additional layers of critical information:
- Type Information: Every node in the tree is enriched with comprehensive type data, similar to the output of a compiler.
- Formatting Preservation: Uniquely, the LST captures all original formatting, including whitespace and comments.
This architecture allows OpenRewrite to parse code, apply transformations, and write it back to the source file with character-for-character fidelity to the original style, provided no changes were intended. Most importantly, these modifications are deterministic; a “recipe”—the rule-based transformer used by the engine—will produce identical results every time it is applied, enabling mass application across thousands of repositories without the need for exhaustive manual re-verification.
Methodology: Combining AI with Rule-Based Transformers
The most effective strategy for large-scale remediation involves a hybrid approach that leverages both AI and deterministic tools. In this model, AI agents are used to assist human developers in generating the refactoring recipes themselves. Once a recipe is refined and tested, it acts as a reliable, version-controlled asset that can be executed at scale.
OpenRewrite’s ecosystem is divided into open-source and commercial components. The core engine and a vast catalog of common recipes—covering framework migrations (such as Spring Boot upgrades), security fixes, and stylistic consistency—are available under the Apache license. For large-scale enterprise management, the Moderne platform provides advanced capabilities, including:
- SaaS and On-Premise (DX) Options: These allow for mass refactoring across an entire organization’s source code system.
- Semantic Search: By calculating embeddings on LSTs, the platform enables highly sophisticated code intelligence and search.
- Batch Remediation Tracking: A centralized dashboard for managing the progress of large-scale security and tech debt campaigns.
Implementation and Impact
The practical application of these tools has demonstrated a 4X increase in productivity for security vulnerability remediation at major corporations. Beyond security, use cases include technical modernization, library upgrades, and maintaining architectural standards. By automating the “grunt work” of refactoring, senior engineers can focus on higher-level architectural decisions while the deterministic engine ensures that thousands of microservices remain up-to-date with the latest security patches and framework versions.
Relevant links and hashtags:
[MunchenJUG] Reliability in Enterprise Software: A Critical Analysis of Automated Testing in Spring Boot Ecosystems (27/Oct/2025)
Lecturer
Philip Riecks is an independent software consultant and educator specializing in Java, Spring Boot, and cloud-native architectures. With over seven years of professional experience in the software industry, Philip has established himself as a prominent voice in the Java ecosystem through his platform, Testing Java Applications Made Simple. He is a co-author of the influential technical book Stratospheric: From Zero to Production with Spring Boot and AWS, which bridges the gap between local development and production-ready cloud deployments. In addition to his consulting work, he produces extensive educational content via his blog and YouTube channel, focusing on demystifying complex testing patterns for enterprise developers.
Abstract
In the contemporary landscape of rapid software delivery, automated testing serves as the primary safeguard for application reliability and maintainability. This article explores the methodologies for demystifying testing within the Spring Boot framework, moving beyond superficial unit tests toward a comprehensive strategy that encompasses integration and slice testing. By analyzing the “Developer’s Dilemma”—the friction between speed of delivery and the confidence provided by a robust test suite—this analysis identifies key innovations such as the “Testing Pyramid” and specialized Spring Boot test slices. The discussion further examines the technical implications of external dependency management through tools like Testcontainers and WireMock, advocating for a holistic approach that treats test code with the same rigor as production logic.
The Paradigm Shift in Testing Methodology
Traditional software development often relegated testing to a secondary phase, frequently outsourced to separate quality assurance departments. However, the rise of DevOps and continuous integration has necessitated a shift toward “test-driven” or “test-enabled” development. Philip Riecks identifies that the primary challenge for developers is not the lack of tools, but the lack of a clear strategy. Testing is often perceived as a bottleneck rather than an accelerator.
The methodology proposed focuses on the Testing Pyramid, which prioritizes a high volume of fast, isolated unit tests at the base, followed by a smaller number of integration tests, and a minimal set of end-to-end (E2E) tests at the apex. The innovation in Spring Boot testing lies in its ability to provide “Slice Testing,” allowing developers to load only specific parts of the application context (e.g., the web layer or the data access layer) rather than the entire infrastructure. This approach significantly reduces test execution time while maintaining high fidelity.
Architectural Slicing and Context Management
One of the most powerful features of the Spring Boot ecosystem is its refined support for slice testing via annotations. This allows for an analytical approach to testing where the scope of the test is strictly defined by the architectural layer under scrutiny.
- Web Layer Testing: Using
@WebMvcTest, developers can test REST controllers without launching a full HTTP server. This slice provides a mocked environment where the web infrastructure is active, but business services are replaced by mocks (e.g., using@MockBean). - Data Access Testing: The
@DataJpaTestannotation provides a specialized environment for testing JPA repositories. It typically uses an in-memory database by default, ensuring that database interactions are verified without the overhead of a production-grade database. - JSON Serialization:
@JsonTestisolates the serialization and deserialization logic, ensuring that data structures correctly map to their JSON representations.
This granular control prevents “Context Bloat,” where tests become slow and brittle due to the unnecessary loading of the entire application environment.
Code Sample: A Specialized Controller Test Slice
@WebMvcTest(UserRegistrationController.class)
class UserRegistrationControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserRegistrationService registrationService;
@Test
void shouldRegisterUserSuccessfully() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"username\": \"priecks\", \"email\": \"philip@example.com\"}"))
.andExpect(status().isCreated());
}
}
Managing External Dependencies: Testcontainers and WireMock
A significant hurdle in integration testing is the reliance on external systems such as databases, message brokers, or third-party APIs. Philip emphasizes the move away from “In-Memory” databases (like H2) for testing production-grade applications, citing the risk of “Environment Parity” issues where H2 behaves differently than a production PostgreSQL instance.
The integration of Testcontainers allows developers to spin up actual Docker instances of their production infrastructure during the test lifecycle. This ensures that the code is tested against the exact same database engine used in production. Similarly, WireMock is utilized to simulate external HTTP APIs, allowing for the verification of fault-tolerance mechanisms like retries and circuit breakers without depending on the availability of the actual external service.
Consequences of Testing on Long-term Maintainability
The implications of a robust testing strategy extend far beyond immediate bug detection. A well-tested codebase enables fearless refactoring. When developers have a “safety net” of automated tests, they can update dependencies, optimize algorithms, or redesign components with the confidence that existing functionality remains intact.
Furthermore, Philip argues that the responsibility for quality must lie with the engineer who writes the code. In an “On-Call” culture, the developer who builds the system also runs it. This ownership model, supported by automated testing, transforms software engineering from a process of “handing over” code to one of “carefully crafting” resilient systems.
Conclusion
Demystifying Spring Boot testing requires a transition from viewing tests as a chore to seeing them as a fundamental engineering discipline. By leveraging architectural slices, managing dependencies with Testcontainers, and adhering to the Testing Pyramid, developers can build applications that are not only functional but also sustainable. The ultimate goal is to reach a state where testing provides joy through the confidence it instills, ensuring that the software remains a robust asset for the enterprise rather than a source of technical debt.
Links:
Clojure: A Modern Lisp for Durable, Concurrent Software
Clojure: A Modern Lisp for Durable, Concurrent Software
In the evolving landscape of programming languages, Clojure distinguishes itself not through novelty or aggressive feature growth, but through deliberate restraint. Rather than chasing trends, it revisits enduring principles of computer science—immutability, functional composition, and symbolic computation—and applies them rigorously to contemporary software systems. This approach results in a language that feels both deeply rooted in tradition and sharply attuned to modern challenges.
Clojure appeals to developers and organizations that prioritize long-term correctness, conceptual coherence, and system resilience over short-term convenience.
What Clojure Is and What It Aims to Solve
Clojure is a functional, dynamically typed programming language that runs primarily on the Java Virtual Machine. It is a modern Lisp, and as such it adopts a uniform syntax in which code is represented as structured data. This design choice enables powerful programmatic manipulation of code itself, while also enforcing consistency across the language.
Unlike many earlier Lisp dialects, Clojure was explicitly designed for production systems. It assumes the presence of large codebases, multiple teams, and long-lived services. As a result, its design is deeply influenced by concerns such as concurrency, data integrity, and integration with existing ecosystems.
Historical Context and Design Motivation
Rich Hickey introduced Clojure publicly in 2007 after years of observing recurring failures in large software systems. His critique focused on the way mainstream languages conflate identity, state, and value. In mutable systems, objects change over time, and those changes must be coordinated explicitly when concurrency is involved. The resulting complexity often exceeds human reasoning capacity.
Clojure responds by redefining the problem. Instead of allowing values to change, it treats values as immutable and represents change as a controlled transition between values. This shift in perspective underpins nearly every aspect of the language.
Immutability as a Foundational Principle
In Clojure, immutability is the default. Data structures such as vectors, maps, and sets never change in place. Instead, operations that appear to modify data return new versions that share most of their internal structure with the original.
(def user {:name "Alice" :role "admin"})
(def updated-user (assoc user :role "editor"))
;; user remains unchanged
;; updated-user reflects the new role
Because values never mutate, functions cannot introduce hidden side effects. This dramatically simplifies reasoning, testing, and debugging, especially in concurrent environments.
Functional Composition in Practice
Clojure encourages developers to express computation as the transformation of data through a series of functions. Rather than focusing on control flow and state transitions, programs describe what should happen to data.
(defn even-squares [numbers]
(->> numbers
(filter even?)
(map #(* % %))))
In this example, data flows through a pipeline of transformations. Each function is small, focused, and easily testable, which encourages reuse and composability over time.
Concurrency Through Explicit State Management
Clojure’s concurrency model separates identity from value. State is managed through explicit reference types, while the values themselves remain immutable. This design makes concurrent programming safer and more predictable.
(def counter (atom 0))
(swap! counter inc)
For coordinated updates across multiple pieces of state, Clojure provides software transactional memory, allowing several changes to occur atomically.
(def account-a (ref 100))
(def account-b (ref 50))
(dosync
(alter account-a - 10)
(alter account-b + 10))
Macros and Language Extension
Because Clojure code is represented as data, macros can transform programs before evaluation. This allows developers to introduce new syntactic constructs that feel native to the language rather than external utilities.
(defmacro unless [condition & body]
`(if (not ~condition)
(do ~@body)))
Although macros should be used with care, they play an important role in building expressive and coherent abstractions.
Interoperability with Java
Despite its distinct philosophy, Clojure integrates seamlessly with Java. Java classes can be instantiated and methods invoked directly, allowing developers to reuse existing libraries and infrastructure.
(import java.time.LocalDate)
(LocalDate/now)
Comparison with Java
Although Clojure and Java share the JVM, they differ fundamentally in how they model software. Java emphasizes object-oriented design, mutable state, and explicit control flow. Clojure emphasizes immutable data, functional composition, and explicit state transitions.
While Java has incorporated functional features over time, its underlying model remains object-centric. Clojure offers a more radical rethinking of program structure, often resulting in smaller and more predictable systems.
Comparison with Scala
Scala and Clojure are often compared as functional alternatives on the JVM, yet their philosophies diverge significantly. Scala embraces expressive power through advanced typing and rich abstractions, whereas Clojure seeks to reduce complexity by minimizing the language itself.
Both approaches are valid, but they reflect different beliefs about how developers best manage complexity.
Closing Perspective
Clojure is not designed for universal adoption. It demands a shift in how developers think about state, time, and behavior. However, for teams willing to embrace its principles, it offers a disciplined and coherent approach to building software that remains understandable, correct, and adaptable over time.