Recent Posts
Archives

Posts Tagged ‘Java’

PostHeaderIcon [MunchenJUG] Architectural Decoupling: Practical Implementations of Persistence in Clean Architecture (13/May/2024)

Lecturer

Daniel Istvan Buza is a Senior Software Engineer and Technical Lead with extensive experience in the Java ecosystem. Currently leading two development teams, Daniel focuses on mentoring, code reviews, and hosting coding dojos to promote high-quality software craftsmanship. His technical expertise encompasses a wide array of technologies, including Microservices, Spring, Angular, Kafka, and MongoDB. Beyond his professional role, he is a frequent contributor to technical discourse, sharing insights through platforms like DZone and GitHub.

Abstract

While the theoretical foundations of Clean Architecture have been widely discussed since its inception by Robert C. Martin, practical implementation details—particularly concerning persistence—often remain elusive. This article examines the methodologies for decoupling business logic from technical infrastructure within Java-based systems. It explores the “dependency rule,” the strategic value of the business core, and the implications of making the domain agnostic of specific languages or frameworks. Central to this analysis is a non-standard, property-based approach to defining persistence APIs, designed to enhance modularity and maintainability in complex software environments.

The Philosophical Core of Clean Architecture

The essence of Clean Architecture lies in the strict management of dependencies, where the business core remains isolated from external technical influences. Robert Martin, often referred to as Uncle Bob, formalized this concept in 2012, emphasizing that business logic should not depend on the database, UI, or even the underlying programming language.

This decoupling is not merely a technical preference but a strategic business decision. In a software project, the primary value resides in the business rules. A system with a fully functional business core but no UI or database is more valuable and marketable than a system with a polished interface but no logic. By treating the business core as the primary asset, developers can defer decisions about specific frameworks or storage technologies, ensuring the system remains flexible and adaptable to future requirements.

Language and Framework Independence

A rigorous application of Clean Architecture suggests that the domain should ideally be expressed in a way that is independent of specific programming languages. While most systems are implemented in languages like Java or Python, some specialized environments, such as tax office systems, utilize domain-specific languages (DSLs) to encapsulate complex rules. This approach ensures that changes in the technical stack—such as upgrading a framework or migrating to a different runtime—do not force a redesign of the business logic.

Property-Based Persistence APIs

A significant challenge in implementing Clean Architecture is the interface between the domain and the persistence layer. Traditional approaches often couple the domain to specific database structures. A more flexible methodology involves defining a persistence API based on properties rather than fixed entities. This allows the domain to specify what data is needed without prescribing how it should be stored or retrieved.

By utilizing Java language features effectively, developers can create persistence abstractions that are both expressive and decoupled. This modularity facilitates easier testing, as the business logic can be verified in isolation using mocks or in-memory repositories without the overhead of a real database.

Conclusion

Transitioning from the theory of Clean Architecture to a sustainable implementation requires a disciplined approach to dependency management. By prioritizing the business core and utilizing property-based persistence abstractions, development teams can build systems that are resilient to technical churn. The ultimate goal is to create a codebase where the most valuable part of the software—its logic—is protected from the inevitable evolution of external tools and frameworks.

Links:

PostHeaderIcon [VoxxedDaysAmsterdam2026] Stream Tricks That You Don’t Wanna Miss: Enhancing Java Streams with Gatherers and String Templates in JDK 25

Lecturer
Aicha Laafia is a Java software engineer at Havana Group, currently based in France while originally from Morocco. She is passionate about sustainable technology, green programming, and advocating for greater representation of women in tech. Aicha actively participates in various communities, serves as a Women Techmakers and Girls Code ambassador, and facilitates IAmRemarkable workshops. She was recently promoted to Oracle ACE Associate, recognizing her contributions to the Java ecosystem.

Abstract
In this engaging session from Voxxed Days Amsterdam 2026, Aicha Laafia explores significant enhancements to Java’s stream processing capabilities and string handling introduced in JDK 25. She addresses longstanding pain points with traditional streams—such as the inability to maintain state mid-pipeline, complex custom collectors for batching or sliding windows, and error-prone string concatenation for SQL, JSON, or logs—through the new Stream Gatherers API and String Templates. Drawing on live code demonstrations and relatable examples from Formula 1 racing data, the presentation illustrates how these features introduce memory and statefulness to streams, simplify data transformations, and promote safer, more readable code. The talk underscores Java’s continued evolution toward more expressive and maintainable programming paradigms, encouraging developers to upgrade and share knowledge about these advancements.

The Persistent Challenges with Traditional Java Streams

Java developers have long appreciated streams for producing cleaner, more declarative, and expressive code compared to imperative loops. However, as Aicha points out, streams can occasionally leave programmers feeling frustrated or even “like complete idiots” when attempting advanced operations. The core limitation stems from the stateless nature of intermediate operations like map, filter, or flatMap. Each element processes independently and is immediately forgotten, making it impossible to track accumulated state, create overlapping windows, or group data mid-pipeline without terminating the stream via a collector.

Common pain points include manual batching implementations that rely on counters, lists, and careful index management to avoid off-by-one errors or lost elements. Grouping overlapping data—for instance, creating sliding windows of size n for rolling averages or trend detection—often requires intricate custom collectors that become difficult to understand or maintain over time, even for the original author. Furthermore, once a collector is applied, the pipeline ends; no further stream operations are possible afterward. These issues lead to verbose, error-prone code or a reluctant fallback to traditional for-loops, undermining the very benefits streams were meant to deliver.

Aicha emphasizes that these problems arise because prior to JDK 25, streams lacked “memory.” Elements flowed through independently without retaining context from previous items, forcing developers into workarounds that compromised readability and maintainability.

Introducing Stream Gatherers: Bringing Memory and Flexibility to Streams

JDK 25 addresses these limitations head-on with the Stream Gatherers API, which equips streams with stateful processing capabilities while remaining intermediate operations. Unlike terminal collectors, gatherers allow continued chaining after stateful transformations. A gatherer consists of up to four components, though only the integrator is mandatory:

  • Initializer (optional): Executes once before any elements arrive, establishing initial state such as an empty list or counter.
  • Integrator: The core logic, invoked for every element. It receives the current element, the mutable state, and a downstream consumer. Developers implement accumulation or transformation here, returning true to continue or false to short-circuit the pipeline.
  • Combiner (optional): Essential for parallel streams, merging partial states from different threads.
  • Finisher (optional): Runs once at the end of the stream, ensuring no residual state (such as an incomplete final batch) is lost by pushing any remaining elements downstream.

This design provides a short-circuit mechanism and supports parallel execution when a combiner is supplied. Aicha demonstrates creating a custom batching gatherer in roughly 15 lines of code—far simpler than equivalent custom collectors or manual loops. The initializer creates an empty list; the integrator adds elements until the batch size is reached, then pushes the batch downstream and clears the buffer; the finisher handles any trailing incomplete batch.

Even better, JDK 25 ships with five built-in gatherers that eliminate most custom implementations:

  • windowFixed(n): Produces non-overlapping batches of exactly size n, including a final potentially smaller batch.
  • windowSliding(n): Generates overlapping windows, ideal for rolling calculations, trend detection, or analyzing sequential data patterns in production monitoring.
  • scan: Accumulates intermediate results similar to a fold, emitting every partial value starting from an initial element—unlike reduce, which yields only the final result.
  • fold: Similar accumulation but treats the operation as intermediate, returning an Optional while permitting further pipeline chaining.
  • mapConcurrent(maxConcurrency, mapper): Executes the mapper on virtual threads (up to the specified concurrency limit) while preserving encounter order, making it particularly suited for I/O-bound tasks without manual thread management.

These tools transform previously cumbersome tasks into concise, readable one- or few-line operations.

Live Demonstration: Analyzing Formula 1 Data with Gatherers

To illustrate practical application, Aicha uses racing data from Max Verstappen’s 2025 Formula 1 season, modeled as a record containing round number, Grand Prix name, position, and points. She contrasts traditional approaches—often involving dozens of lines of custom collector code with initializer, accumulator, combiner, and finisher—with gatherer-based solutions.

For batching every three races to compute cumulative points and wins, a windowFixed(3) gatherer replaces extensive custom logic, producing clean batches while automatically handling the final incomplete group. Sliding windows demonstrate overlapping views, such as performance trends across consecutive race triplets, again in just a few lines.

Accumulation across the entire season uses scan to emit running totals after each race, revealing Verstappen’s final 421 points and near-miss championship outcome. These examples highlight how gatherers retain “memory” of prior elements, enabling stateful yet fluent pipelines.

Aicha also touches on String Templates, another JDK 25 feature that enhances safety and readability. Traditional string concatenation or String.format often leads to injection vulnerabilities in SQL or JSON and creates “plus soup” that is hard to read. String Templates provide a clean, type-safe interpolation mechanism that reduces errors and improves security for logging, queries, and data serialization.

Implications and Recommendations for Modern Java Development

The introduction of gatherers and string templates reflects Java’s ongoing commitment to evolving without breaking compatibility, offering developers more powerful abstractions while preserving the language’s robustness. By reducing reliance on custom collectors and imperative workarounds, these features promote more maintainable, expressive codebases that are easier to reason about and debug.

Gatherers particularly shine in data processing pipelines, analytics, monitoring, and any domain requiring windowed or accumulated views. Their support for parallelism and short-circuiting adds efficiency, while the built-in variants cover the majority of common use cases, lowering the barrier to advanced stream usage.

Aicha encourages the community to upgrade to the latest JDK, experiment with these capabilities, write articles, and deliver talks to spread awareness. She notes that many scenarios previously abandoned to “for-loop hell” now become elegant stream solutions thanks to gatherers.

Code Sample: Batching with windowFixed

// Traditional complex collector approach omitted for brevity

// With Gatherers in JDK 25
var batches = races.stream()
    .gather(Gatherers.windowFixed(3))
    .map(batch -> computeStats(batch))  // e.g., sum points, count wins
    .toList();

Code Sample: Sliding Window for Trends

var slidingWindows = races.stream()
    .gather(Gatherers.windowSliding(3))
    .map(window -> analyzeTrend(window))
    .toList();

Code Sample: Accumulation with scan

var runningTotals = pointsStream
    .gather(Gatherers.scan(() -> 0, Integer::sum))
    .toList();  // Emits every intermediate sum

These snippets demonstrate the dramatic reduction in complexity while preserving full pipeline fluency.

In conclusion, Aicha Laafia’s presentation provides both a clear diagnosis of historical stream limitations and a compelling vision for their resolution in JDK 25. By incorporating statefulness through gatherers and safer string handling, Java strengthens its position as a modern, versatile language suitable for complex data-driven applications. Developers who adopt these features will benefit from shorter, more readable code, fewer maintenance headaches, and enhanced productivity.

Links:

PostHeaderIcon [MiamiJUG] Specialization and Efficiency: The Future of Distilled Models and MoE

Lecturer

Frank Greco is a distinguished Java Champion and enterprise architect with a deep focus on AI, Cloud, and Edge computing. As a senior consultant and long-standing educator, he chairs the NYJavaSIG and has co-authored industry standards such as JSR #381. Frank is dedicated to helping developers navigate the practical implementation of machine learning within enterprise ecosystems.

Abstract

As generative AI moves from experimental prototypes to enterprise production, the focus has shifted from monolithic models to specialized architectures. This article analyzes two critical trends: Distilled Models and Mixture of Experts (MoE). By exploring how large models can “teach” smaller, more efficient versions and how sub-networks can be orchestrated to handle niche tasks, this study provides a roadmap for building cost-effective, high-performance AI applications in memory-constrained environments.

The Methodology of Model Distillation

The current evolution of AI prioritizes efficiency and latency over raw parameter count. Model distillation is a process where a large, high-parameter model (the “Teacher”) is used to train a significantly smaller model (the “Student”).

The technical process involves:

  1. Reasoning Extraction: The teacher model is prompted to solve problems using Chain of Thought (CoT) reasoning.
  2. Pattern Learning: The student model is trained on the teacher’s thought process and step-by-step logic.
  3. Optimization: The resulting student model—such as the DeepSeek variants—retains much of the reasoning capability of the larger model while requiring significantly less memory and providing faster response times.

This is particularly relevant for Java developers who need to deploy AI features in environments where the infrastructure costs of running a massive LLM would be prohibitive.

Mixture of Experts (MoE) Architecture

Beyond distillation, the industry is transitioning toward “Mixture of Experts” (MoE) architectures. Instead of one massive, uniform neural network, an MoE system consists of a collection of specialized sub-networks.

In this configuration, a “router” analyzes the incoming prompt and determines which “expert” sub-network is best suited to answer. For instance, a technical query about Java garbage collection would be routed to a code-specialized network, whereas a question about financial regulation would go to a legal-specialized expert. This approach ensures higher precision and reduces the total active parameters needed for a single query, leading to more efficient processing at scale.

Conclusion: The Developer as Orchestrator

The emergence of these specialized architectures changes the role of the enterprise developer. Rather than simply querying a single general-purpose model, developers must now act as orchestrators, selecting the right combination of distilled models and expert networks for their specific domain. By understanding these architectural shifts, engineers can build AI-integrated systems that are both powerful and economically viable for large-scale production.

Links:

PostHeaderIcon [MunchenJUG] Advanced Automated Testing: Navigating the Integration Frontier (13/May/2024)

Lecturer

Daniel Istvan Buza is an accomplished Senior Software Engineer and Technical Lead with extensive experience in architecting Java-based ecosystems. His expertise spans a broad spectrum of technologies, including Spring, Angular, Kafka, MongoDB, and Microservices. As a leader of multiple development teams, Daniel focuses on enhancing code quality through initiatives like coding dojos and rigorous peer reviews. He is a dedicated mentor within the software community, constantly exploring innovative methodologies to bridge the gap between development and quality assurance.

Abstract

This article analyzes the transition from traditional unit testing to comprehensive acceptance and end-to-end (E2E) testing frameworks. It utilizes a real-world case study of a “silent” frontend-backend failure to illustrate why high test coverage often fails to detect integration defects. The discussion centers on the implementation of Playwright as a primary tool for stateful and stateless testing within a Java environment. By evaluating strategies for mocking external dependencies like Kafka and S3, and addressing common pitfalls such as internationalization and time zone sensitivities, this analysis provides a technical roadmap for building resilient CI/CD pipelines that go beyond the limitations of isolated component tests.

The Vulnerability of Isolated Testing

A critical challenge in modern web development is the “integration gap”—a scenario where backend and frontend components pass their respective unit tests but fail when operating in tandem. A common example involves dynamic attribute renaming: if a backend developer renames a field in a DTO (Data Transfer Object) and updates the corresponding tests, the backend remains “green.” However, if the frontend is not simultaneously updated to expect the new attribute name, the UI may fail to display data correctly, resulting in empty columns or broken features that are highly visible to users but invisible to isolated test suites.

This discrepancy highlights a fundamental truth: test coverage does not equate to test quality. Even 100% coverage cannot guarantee system correctness if the interactions between disparate services are not explicitly verified. To address this, teams must move toward “Acceptance Tests” (AC tests) that simulate actual user interactions across the entire stack.

Leveraging Playwright for Java-Centric Environments

For teams primarily composed of backend developers, selecting a testing tool that integrates seamlessly with the existing Java ecosystem is paramount. Playwright, a framework developed by Microsoft, has emerged as a robust solution due to its native Java support and its ability to automate browsers like Chrome, Firefox, and Safari.

Core Interactions in E2E Testing

The majority of web application functionality can be verified through four fundamental interaction types:

  1. Clicking: Interacting with buttons, links, and navigation elements.
  2. Input: Filling text and search fields.
  3. Assertion: Verifying visual properties, such as the presence, color, or size of elements.
  4. File Operations: Managing the upload and download of documents.

By focusing on these interactions, developers can create scripts that mirror user behavior, ensuring that the “happy path” of the application remains functional regardless of internal refactoring.

Architectural Strategies: Stateful vs. Stateless Testing

When implementing E2E tests, developers must choose between two primary architectural approaches: stateful and stateless testing.

Stateful (Environment-Targeted) Testing

In this model, tests are executed against a persistent environment with a shared database and live external APIs. This approach is highly realistic but introduces the risk of “pollution,” where data left by one test affects the outcome of subsequent tests. It requires rigorous cleanup procedures to maintain environment stability.

Stateless (Containerized) Testing

Stateless testing involves spinning up a fresh, full-featured frontend-backend pair within a CI/CD pipeline for every test run. This often utilizes embedded databases (e.g., MongoDB) and mocks for external dependencies like S3 buckets or Kafka topics. While more complex to set up, this method provides total isolation and reproducibility. However, it requires careful management of operating system dependencies within the test containers to ensure Playwright can execute the browsers correctly.

Technical Pitfalls and Best Practices

The transition to advanced automated testing reveals several subtle challenges that can undermine test reliability.

  • Internationalization (i18n): Relying on UI text for selectors can lead to massive test failures when translation files are updated. Using unique element IDs is a safer alternative, though it may limit the ability to verify that the correct error messages are being displayed to the user.
  • Time Zone Sensitivity: UI elements displaying timestamps will vary based on the local environment. Playwright allows developers to explicitly specify a locale to ensure consistent assertions across geographically distributed teams.
  • Synchronicity: A major source of test flakiness is the misuse of Thread.sleep(). Developers should instead utilize Playwright’s built-in “wait for condition” methods to handle asynchronous backend tasks, which are more resilient to varying network or processing speeds.

Conclusion

Modern software delivery requires a testing strategy that transcends the unit level. By integrating Playwright into the Java development lifecycle, teams can automate complex user journeys and bridge the gap between frontend and backend. While no single testing setup is superior, a combination of stateful environment checks and isolated CI/CD pipelines provides the most comprehensive defense against integration defects. Developers are encouraged to treat their test code with the same rigor as production code, striving for cleanliness and maintainability to ensure long-term system reliability.

Links:

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.