Recent Posts
Archives

Posts Tagged ‘Java’

PostHeaderIcon [MiamiJUG] Retrieval-Augmented Generation: Building Deterministic AI for Production

Lecturer

Frank Greco is a Java Champion, enterprise architect, and senior consultant specializing in Artificial Intelligence and Cloud computing. He is the founder and Chairman of NYJavaSIG and a co-author of JSR #381 “VisRec,” the Java API for visual recognition. Frank is a recognized educator and technical leader who has presented at major global conferences including JavaOne, DevNexus, and Devoxx.

Abstract

This article provides an analytical framework for integrating Large Language Models (LLMs) into production Java environments using Retrieval-Augmented Generation (RAG). By moving beyond simple chat interfaces to programmatic API access, developers can build AI systems that are grounded in verified enterprise data. The analysis explores prompt engineering methodologies—such as Few-Shot and Chain of Thought (CoT)—and the architectural role of vector databases in mitigating model hallucinations while ensuring data security and version control.

Methodologies in Prompt Engineering

Prompting is the primary mechanism for steering the behavior of a neural network. Unlike traditional programming, prompting is probabilistic rather than deterministic. Frank identifies several advanced techniques to improve model reliability:

  • Zero-Shot and Few-Shot Learning: Few-shot prompting provides the model with specific examples of the desired input-output pattern, significantly improving the accuracy of complex tasks.
  • Chain of Thought (CoT): This instructs the model to “think step-by-step,” detailing its reasoning process before providing a final answer. This methodology is critical for reducing logical errors.
  • Persona Identification: Assigning a specific role to the model (e.g., “Act as a Java security expert”) helps contextualize the response and refine the output tone.

Architectural Implementation: Retrieval-Augmented Generation (RAG)

To overcome the limitations of an LLM’s static training data, enterprises utilize RAG to ground the model in real-time, private data. In a RAG architecture, a user query is first used to search a knowledge base—typically a Vector Database—for relevant documents. This retrieved context is then injected into the prompt, allowing the LLM to generate an answer based on specific facts rather than general probabilities.

This approach offers several production-grade benefits:

  1. Reduced Hallucinations: By providing the model with the necessary facts, the likelihood of it “making up” information is significantly decreased.
  2. Data Security: RAG allows models to use private company information without that data being used to train the underlying public model.
  3. Traceability: Responses can be cited back to specific source documents found in the vector database.

Production Challenges and Ethical Considerations

Implementing AI at scale introduces significant engineering overhead. Developers must manage Prompt Versioning to ensure consistent behavior across deployments and navigate the legal implications of AI-generated content. Furthermore, because these are probabilistic systems, Frank warns that if a wrong answer poses a high risk to the business, generative AI may not be the appropriate solution. Engineers must balance the productivity gains of AI with the need for rigorous safety guardrails and human-in-the-loop verification.

Links:

PostHeaderIcon [MunchenJUG] Strategic Approaches to Mitigating Software Defects in Java Development (08/Jul/2025)

Lecturer

Tagir Valeev is a distinguished software engineer and a prominent figure in the Java ecosystem, currently serving as a Technical Lead at JetBrains. His professional focus lies in the advancement of Java static analysis within IntelliJ IDEA, a critical tool for automated bug detection. Tagir is an OpenJDK committer and a Java Champion, honors that reflect his deep technical contributions to the language’s core. He is also the author of the authoritative text “100 Java Mistakes and How to Avoid Them”, which systematically classifies common programming errors.

Abstract

The pervasive nature of software defects necessitates a multi-layered defense strategy rather than a single technical solution. This article examines the methodology for reducing bug density in Java applications by exploring the classification of “tiny but disastrous” repeatable errors. Central to this analysis is the “Swiss Cheese Model” of software quality, which posits that a combination of independent defensive layers—such as static analysis, unit testing, and code review—is significantly more effective than over-investing in any single approach. By investigating real-world code snippets and the limitations of 100% test coverage, this study provides a framework for developers to understand the trade-offs and synergies between modern quality assurance tools.

The Taxonomy of Modern Software Defects

Software bugs vary significantly in complexity and scope. While large-scale architectural failures often make for compelling post-mortem analyses, the majority of developer time is occupied by tiny, local errors. These defects, though appearing minor—such as a single incorrect character or an erroneous one-line construct—can lead to catastrophic system failures in production.

The critical characteristic of these small-scale bugs is their repeatability. Because they recur across different projects and developers, they can be systematically classified and studied. Understanding these patterns allows developers to proactively identify potential pitfalls during the implementation phase. Furthermore, repetition is often the catalyst for such errors; copying and pasting code blocks without rigorous verification is a frequent source of “repeatable” defects that elude casual observation.

The Limitations of Individual Quality Assurance Layers

A common misconception in software engineering is the belief in a “Silver Bullet”—a single technique, such as Test-Driven Development (TDD) or advanced static analysis, that can eliminate all defects. Empirical evidence suggests that each individual layer of defense eventually reaches a plateau of efficiency.

The Paradox of Total Test Coverage

Striving for 100% test coverage often results in diminishing returns. In complex libraries, achieving the final percentages of coverage can require significantly more effort than the actual implementation of the feature. Moreover, high coverage metrics do not guarantee the absence of bugs; code that is executed during a test run can still contain logical flaws that the test assertions fail to capture.

Static Analysis and Code Review

Static analysis tools like FindBugs (now SpotBugs) and the integrated analyzers in modern IDEs offer the “revelation” of finding bugs without code execution. However, these tools are not infallible, as they are subject to both false positives—reporting errors where none exist—and false negatives—failing to detect actual issues. Similarly, code reviews and pair programming provide essential human oversight, but they are limited by the reviewers’ cognitive load and familiarity with the specific bug patterns being introduced.

The Swiss Cheese Model of Defensive Programming

The most effective strategy for defect mitigation is derived from the “Swiss Cheese Model,” originally applied in aviation and medical engineering. This model represents each defensive technique as a slice of Swiss cheese; while each slice has “holes” (limitations or specific types of bugs it cannot catch), stacking multiple slices significantly reduces the likelihood that a defect will pass through all layers into production.

In a robust development pipeline, these layers typically include:

  • Static Analysis: Catching syntactical and common logical patterns early.
  • Code Review/Pair Programming: Leveraging peer insight to spot errors that automated tools might miss.
  • Unit and Integration Testing: Verifying functional requirements and edge cases.
  • Emerging AI Tools: Utilizing modern large language models to provide an additional, albeit experimental, layer of scrutiny.

By distributing resources across these diverse layers, teams can ensure that if one layer fails, another is likely to intervene.

Conclusion

Mitigating software bugs is an “endless struggle” that cannot be completely won, but it can be managed through strategic, diversified defenses. Rather than seeking a single bulletproof solution, developers should focus on understanding repeatable bug patterns and implementing a multi-layered quality assurance process. The integration of specialized static analysis, thorough peer review, and balanced testing creates a resilient ecosystem capable of catching disastrous errors before they impact the end user.

Links:

PostHeaderIcon [VoxxedDaysBucharest2026] Mastering Performance Optimization in Java: Roberto Cortez on Writing Efficient Code

Lecturer

Roberto Cortez is a Senior Software Engineer at Red Hat and a prominent contributor to the Quarkus project, with particular expertise in configuration systems, startup performance, and runtime efficiency optimizations. With years of experience in Java development and cloud-native technologies, Roberto focuses on making Java applications faster, more resource-efficient, and better suited for modern deployment environments.

Abstract

In many development projects, functional delivery takes precedence while performance considerations are deferred until bottlenecks become apparent. Roberto Cortez challenges this approach through a detailed examination of efficient Java coding practices. Using real-world examples from Quarkus development, he demonstrates essential tools including Async Profiler for visualization, JMH for benchmarking, and Java Flight Recorder. Through iterative optimization of concrete code examples, he illustrates the importance of measurement, analysis, and continuous refinement.

The Perils of Assumption and the Imperative of Measurement

Roberto draws from his extensive work on Quarkus configuration loading to highlight how seemingly minor implementation details can have outsized performance impacts. He gently critiques the common misinterpretation of Donald Knuth’s famous quote about premature optimization, clarifying that while not every piece of code requires micro-optimization, developers must remain vigilant about critical execution paths that significantly affect user experience or resource consumption.

A central example involves a simple string prefixing operation implemented using Java Streams. While the code appears clean and idiomatic, profiling reveals substantial hidden costs in object allocations and temporary structures. This serves as a powerful reminder that intuition alone is insufficient — empirical measurement must guide optimization decisions.

Profiling with Async Profiler and Flame Graphs

Async Profiler emerges as a key tool due to its low overhead and rich visualization capabilities. When attached to a running Quarkus endpoint responsible for generating lists of names, the resulting flame graphs clearly highlight hotspots in StringBuilder usage and intermediate object creation. These visualizations prove invaluable for understanding complex runtime behavior where application code often represents only a small fraction of total execution time due to framework, JVM, and library interactions.

Roberto demonstrates practical usage patterns and interpretation techniques that enable developers to quickly identify and address performance bottlenecks.

Benchmarking with JMH for Rigorous Comparison

For precise, statistically sound measurements, Roberto turns to the Java Microbenchmark Harness (JMH). He presents detailed benchmarks comparing multiple implementations of the prefixing task: traditional Streams, parallel Streams, manual for-loops, and optimized versions reusing StringBuilder instances. Results across different Java versions (17, 21, and experimental 25) reveal how JVM improvements can render certain hand-optimizations obsolete or even counterproductive.

Additional demonstrations focus on environment variable resolution in Quarkus, where iterative refinements including custom equals and hashCode implementations yield substantial gains in both startup time and memory consumption.

Sustained Vigilance, Real-World Impact, and Lessons Learned

Performance optimization is portrayed as an ongoing discipline rather than a one-time activity. Roberto shares how optimizations introduced in Quarkus 3.5 required revisiting and partial reversion in version 3.6 due to upstream changes. The famous “One Billion Row Challenge” serves as an inspiring example of extreme creativity and technical depth in pursuit of performance.

Key takeaways include focusing optimization efforts on high-impact areas, balancing readability and maintainability concerns, and maintaining rigorous measurement practices throughout the development lifecycle. Developers are encouraged to cultivate a performance-aware mindset while avoiding premature or counterproductive optimizations.

Links:

PostHeaderIcon [VoxxedDaysBucharest2026] Reflections on a Decade: Andra Ghibutiu and Alex Proca Share Opening Thoughts at Voxxed Days Bucharest 2026

Lecturers

Andra Ghibutiu is the CEO and Co-founder of Beyond Business School, a Senior Legal Advisor, and Managing Partner with a pivotal role in establishing and sustaining Voxxed Days Bucharest from its earliest days. Alex Proca is a Senior Software Developer, entrepreneur, founder of the Incremental Community, and a driving force behind the Bucharest Java User Group. Together, they have organized numerous successful technology events across Romania, fostering vibrant developer communities.

Abstract

Marking both the 10th anniversary and the final edition of Voxxed Days Bucharest, organizers Andra Ghibutiu and Alex Proca deliver heartfelt opening reflections on the conference’s evolution, achievements, and the transition toward more intimate, ongoing community activities. They express deep gratitude to speakers, sponsors, attendees, and the broader Romanian technology community while announcing the continuation of engagement through smaller, regular meetups.

A Decade of Community Building and Evolution

Andra and Alex warmly welcome participants to this emotionally significant milestone event. What began as modest local gatherings under the Bucharest Java User Group banner more than 15 years ago has grown into a respected regional technology conference. Over the years, the initiative expanded significantly to include events in Cluj and Iași, specialized frontend-focused gatherings, and virtual sessions during the challenging pandemic period.

The evolution reflected changing community needs and interests, embracing a broadening spectrum of technologies while maintaining a strong foundation in Java and JVM ecosystems. This final physical edition maintains an intimate scale with two conference rooms, approximately 18 speakers, two keynotes, and a dozen focused sessions, preserving the quality and personal connections that have always characterized the event.

Gratitude, Learnings, and Future Community Initiatives

Andra highlights key learnings from organizing a decade of events and reaffirms the educational mission that has guided the conference. Special recognition is given to the many speakers who have contributed their expertise and time across the years.

Sponsors including Criteo, AD01, Supertree, Copings, and Natsuro receive appreciation for their continued support. The organizers emphasize the dedication of attendees who have made the event possible through their participation and engagement.

Looking forward, the community commitment continues beyond this final large-scale edition. Plans involve transitioning to smaller, more frequent meetups organized through the Luma platform. The next event is already scheduled for May 7th at Stripe offices, ensuring ongoing opportunities for knowledge sharing and networking within the Romanian technology community.

The opening remarks set a reflective yet celebratory tone, honoring a decade of connection, learning, and growth while looking ahead with optimism toward sustained community engagement.

Links:

PostHeaderIcon [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:

  1. Feature Releases: Released every six months, these versions typically receive support for only half a year.
  2. 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:

PostHeaderIcon 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:

  1. Inspects the configured security providers
  2. Searches for available SecureRandom implementations
  3. Selects the preferred implementation
  4. 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
SecureRandom()
, the resulting implementation is explicitly influenced by the platform’s definition of “strong”.

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
SecureRandom()
and 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.

PostHeaderIcon [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:

PostHeaderIcon [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:

PostHeaderIcon [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:

  1. 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.
  2. Performance Optimization: The new version introduces architectural changes that improve the speed of analysis, particularly for large-scale projects with hundreds of rules.
  3. 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:

PostHeaderIcon [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: