Recent Posts
Archives

Posts Tagged ‘Java’

PostHeaderIcon Navigating the Reactive Frontier: Oleh Dokuka’s Reactive Streams at Devoxx France 2023

On April 13, 2023, Oleh Dokuka commanded the Devoxx France stage with a 44-minute odyssey titled “From imperative to Reactive: the Reactive Streams adventure!” Delivered at Paris’s Palais des Congrès, Oleh, a reactive programming luminary, guided developers through the paradigm shift from imperative to reactive programming. Building on his earlier R2DBC talk, he unveiled the power of Reactive Streams, a specification for non-blocking, asynchronous data processing. His narrative was a thrilling journey, blending technical depth with practical insights, inspiring developers to embrace reactive systems for scalable, resilient applications.

Oleh began with a relatable scenario: a Java application overwhelmed by high-throughput data, such as a real-time analytics dashboard. Traditional imperative code, with its synchronous loops and blocking calls, buckles under pressure, leading to latency spikes and resource exhaustion. “We’ve all seen threads waiting idly for I/O,” Oleh quipped, his humor resonating with the audience. Reactive Streams, he explained, offer a solution by processing data asynchronously, using backpressure to balance producer and consumer speeds. Oleh’s passion for reactive programming set the stage for a deep dive into its principles, tools, and real-world applications.

Embracing Reactive Streams

Oleh’s first theme was the core of Reactive Streams: a specification for asynchronous stream processing with non-blocking backpressure. He introduced its four interfaces—Publisher, Subscriber, Subscription, and Processor—and their role in building reactive pipelines. Oleh likely demonstrated a simple pipeline using Project Reactor, a Reactive Streams implementation:

Flux.range(1, 100)
    .map(i -> processData(i))
    .subscribeOn(Schedulers.boundedElastic())
    .subscribe(System.out::println);

In this demo, a Flux emits numbers, processes them asynchronously, and prints results, all while respecting backpressure. Oleh showed how the Subscription controls data flow, preventing the subscriber from being overwhelmed. He contrasted this with imperative code, where a loop might block on I/O, highlighting reactive’s efficiency for high-throughput tasks like log processing or event streaming. The audience, familiar with synchronous Java, leaned in, captivated by the prospect of responsive systems.

Building Reactive Applications

Oleh’s narrative shifted to practical application, his second theme. He explored integrating Reactive Streams with Spring WebFlux, a reactive web framework. In a demo, Oleh likely built a REST API handling thousands of concurrent requests, using Mono and Flux for non-blocking responses:

@GetMapping("/events")
Flux<Event> getEvents() {
    return eventService.findAll();
}

This API, running on Netty and leveraging virtual threads (echoing José Paumard’s talk), scaled effortlessly under load. Oleh emphasized backpressure strategies, such as onBackpressureBuffer(), to manage fast producers. He also addressed error handling, showing how onErrorResume() ensures resilience in reactive pipelines. For microservices or event-driven architectures, Oleh argued, Reactive Streams enable low-latency, resource-efficient systems, a must for cloud-native deployments.

Oleh shared real-world examples, noting how companies like Netflix use Reactor for streaming services. He recommended starting with small reactive components, such as a single endpoint, and monitoring performance with tools like Micrometer. His practical advice—test under load, tune buffer sizes—empowered developers to adopt reactive programming incrementally.

Reactive in the Ecosystem

Oleh’s final theme was Reactive Streams’ role in Java’s ecosystem. Libraries like Reactor, RxJava, and Akka Streams implement the specification, while frameworks like Spring Boot 3 integrate reactive data access via R2DBC (from his earlier talk). Oleh highlighted compatibility with databases like MongoDB and Kafka, ideal for reactive pipelines. He likely demonstrated a reactive Kafka consumer, processing messages with backpressure:

KafkaReceiver.create(receiverOptions)
    .receive()
    .flatMap(record -> processRecord(record))
    .subscribe();

This demo showcased seamless integration, reinforcing reactive’s versatility. Oleh urged developers to explore Reactor’s documentation and experiment with Spring WebFlux, starting with a prototype project. He cautioned about debugging challenges, suggesting tools like BlockHound to detect blocking calls. Looking ahead, Oleh envisioned reactive systems dominating data-intensive applications, from IoT to real-time analytics.

As the session closed, Oleh’s enthusiasm sparked hallway discussions about reactive programming’s potential. Developers left with a clear path: build a reactive endpoint, integrate with Reactor, and measure scalability. Oleh’s adventure through Reactive Streams was a testament to Java’s adaptability, inspiring a new era of responsive, cloud-ready applications.

PostHeaderIcon [DevoxxPL2022] Bare Metal Java • Jarosław Pałka

Jarosław Pałka, a staff engineer at Neo4j, captivated the audience at Devoxx Poland 2022 with an in-depth exploration of low-level Java programming through the Foreign Function and Memory API. As a veteran of the JVM ecosystem, Jarosław shared his expertise in leveraging these experimental APIs to interact directly with native memory and C code, offering a glimpse into Java’s potential for high-performance, system-level programming. His presentation, blending technical depth with engaging demos, provided a roadmap for developers seeking to harness Java’s evolving capabilities.

The Need for Low-Level Access in Java

Jarosław began by contextualizing the necessity of low-level APIs in Java, a language traditionally celebrated for its managed runtime and safety guarantees. He outlined the trade-offs between safety and performance, noting that managed runtimes abstract complexities like memory management but limit optimization opportunities. In high-performance systems like Neo4j, Kafka, or Elasticsearch, direct memory access is critical to avoid garbage collection overhead. Jarosław introduced the Foreign Function and Memory API, incubated since Java 14 and stabilized in Java 17, as a safer alternative to the sun.misc.Unsafe API, enabling developers to work with native memory while preserving Java’s safety principles.

Mastering Native Memory with Memory Segments

Delving into the API’s mechanics, Jarosław explained the concept of memory segments, which serve as pointers to native memory. These segments, managed through resource scopes, allow developers to allocate and deallocate memory explicitly, with safety mechanisms to prevent unauthorized access across threads. He demonstrated how memory segments support operations like setting and retrieving primitive values, using var handles for type-safe access. Jarosław emphasized the API’s flexibility, enabling seamless interaction with both heap and off-heap memory, and its potential to unify access to diverse memory types, including memory-mapped files and persistent memory.

Bridging Java and C with Foreign Functions

A highlight of Jarosław’s talk was the Foreign Function API, which simplifies calling C functions from Java and vice versa. He showcased a practical example of invoking the getpid C function to retrieve a process ID, illustrating the use of symbol lookups, function descriptors, and method handles to map C types to Java. Jarosław also explored upcalls, allowing C code to invoke Java methods, using a signal handler as a case study. This bidirectional integration eliminates the complexities of Java Native Interface (JNI), streamlining interactions with native libraries like SDL for game development.

Practical Applications: A Java Game Demo

To illustrate the API’s power, Jarosław presented a live demo of a 2D game built using Java and the SDL library. By mapping C structures to Java memory layouts, he created sprites and handled events like keyboard inputs, demonstrating how Java can interface with hardware for real-time rendering. The demo highlighted the challenges of manual structure mapping and memory management, but also showcased the API’s potential to simplify these tasks. Jarosław noted that Java 19’s jextract tool automates this process by generating Java bindings from C header files, significantly reducing boilerplate.

Safety and Performance Considerations

Jarosław underscored the API’s safety features, such as temporal and spatial bounds checking, which prevent invalid memory access. He also discussed the cleaner mechanism, which integrates with Java’s garbage collector to manage native memory deallocation. While the API introduces overhead comparable to JNI, Jarosław highlighted its potential for optimization in future releases, particularly for serverless applications and caching. He cautioned developers to use these APIs judiciously, given their complexity and the need for careful error handling.

Future Prospects and Java’s Evolution

Looking ahead, Jarosław positioned the Foreign Function and Memory API as a transformative step in Java’s evolution, enabling developers to write high-performance applications traditionally reserved for languages like C or Rust. He encouraged exploration of these APIs for niche use cases like database development or game engines, while acknowledging their experimental nature. Jarosław’s vision of Java as a versatile platform for both high-level and low-level programming resonated, urging developers to embrace these tools to push the boundaries of what Java can achieve.

Links:

PostHeaderIcon [DevoxxPL2022] Are Immortal Libraries Ready for Immutable Classes? • Tomasz Skowroński

At Devoxx Poland 2022, Tomasz Skowroński, a seasoned Java developer, delivered a compelling presentation exploring the readiness of Java libraries for immutable classes. With a focus on the evolving landscape of Java programming, Tomasz dissected the challenges and opportunities of adopting immutability in modern software development. His talk provided a nuanced perspective on balancing simplicity, clarity, and robustness in code design, offering practical insights for developers navigating the complexities of mutable and immutable paradigms.

The Allure and Pitfalls of Mutable Classes

Tomasz opened his discourse by highlighting the appeal of mutable classes, likening them to a “shy green boy” for their ease of use and rapid development. Mutable classes, with their familiar getters and setters, simplify coding and accelerate project timelines, making them a go-to choice for many developers. However, Tomasz cautioned that this simplicity comes at a cost. As fields and methods accumulate, mutable classes grow increasingly complex, undermining their initial clarity. The internal state becomes akin to a data structure, vulnerable to unintended modifications, which complicates maintenance and debugging. This fragility, he argued, often leads to issues like null pointer exceptions and challenges in maintaining a consistent state, particularly in large-scale systems.

The Promise of Immutability

Transitioning to immutability, Tomasz emphasized its role in fostering robust and predictable code. Immutable classes, by preventing state changes after creation, offer a safeguard against unintended modifications, making them particularly valuable in concurrent environments. He clarified that immutability extends beyond merely marking fields as final or using tools like Lombok. Instead, it requires a disciplined approach to design, ensuring objects remain unalterable. Tomasz highlighted Java records and constructor-based classes as practical tools for achieving immutability, noting their ability to streamline code while maintaining clarity. However, he acknowledged that immutability introduces complexity, requiring developers to rethink traditional approaches to state management.

Navigating Java Libraries with Immutability

A core focus of Tomasz’s presentation was the compatibility of Java libraries with immutable classes. He explored tools like Jackson for JSON deserialization, noting that while modern libraries support immutability through annotations like @ConstructorProperties, challenges persist. For instance, deserializing complex objects may require manual configuration or reliance on Lombok to reduce boilerplate. Tomasz also discussed Hibernate, where immutable entities, such as events or finalized invoices, can express domain constraints effectively. By using the @Immutable annotation and configuring Hibernate to throw exceptions on modification attempts, developers can enforce immutability, though direct database operations remain a potential loophole.

Practical Strategies for Immutable Design

Tomasz offered actionable strategies for integrating immutability into everyday development. He advocated for constructor-based dependency injection over field-based approaches, reducing boilerplate with tools like Lombok or Java records. For RESTful APIs, he suggested mapping query parameters to immutable DTOs, enhancing clarity and reusability. In the context of state management, Tomasz proposed modeling state transitions in immutable classes using interfaces and type-safe implementations, as illustrated by a rocket lifecycle example. This approach ensures predictable state changes without the risks associated with mutable methods. Additionally, he addressed performance concerns, arguing that the overhead of object creation in immutable designs is often overstated, particularly in web-based systems where network latency dominates.

Testing and Tooling Considerations

Testing immutable classes presents unique challenges, particularly with tools like Mockito. Tomasz noted that while Mockito supports final classes in newer versions, mocking immutable objects may indicate design flaws. Instead, he recommended creating real objects via constructors for testing, emphasizing their intentional design for construction. For developers working with legacy systems or external libraries, Tomasz advised cautious adoption of immutability, leveraging tools like Terraform for infrastructure consistency and Java’s evolving ecosystem to reduce boilerplate. His pragmatic approach underscored the importance of aligning immutability with project goals, avoiding dogmatic adherence to either mutable or immutable paradigms.

Embracing Immutability in Java’s Evolution

Concluding his talk, Tomasz positioned immutability as a cornerstone of Java’s ongoing evolution, from records to potential future enhancements like immutable collections. He urged developers to reduce mutation in their codebases and consider immutability beyond concurrency, citing benefits in caching, hashing, and overall design clarity. While acknowledging that mutable classes remain suitable for certain use cases, such as JPA entities in dynamic domains, Tomasz advocated for a mindful approach to code design, prioritizing immutability where it enhances robustness and maintainability.

Links:

PostHeaderIcon [DevoxxPL2022] Integrate Hibernate with Your Elasticsearch Database • Bartosz de Boulange

At Devoxx Poland 2022, Bartosz de Boulange, a Java developer at BGŻ BNP Paribas, Poland’s national development bank, delivered an insightful presentation on Hibernate Search, a powerful tool that seamlessly integrates traditional Object-Relational Mapping (ORM) with NoSQL databases like Elasticsearch. Bartosz’s talk focused on enabling full-text search capabilities within SQL-based applications, offering a practical solution for developers seeking to enhance search functionality without migrating entirely to a NoSQL ecosystem. Through a blend of theoretical insights and hands-on coding demonstrations, he illustrated how Hibernate Search can address complex search requirements in modern applications.

The Power of Full-Text Search

Bartosz began by addressing the challenge of implementing robust search functionality in applications backed by SQL databases. For instance, in a bookstore application, users might need to search for specific phrases within thousands of reviews. Traditional SQL queries, such as LIKE statements, are often inadequate for such tasks due to their limited ability to handle complex text analysis. Hibernate Search solves this by enabling full-text search, which includes character filtering, tokenization, and normalization. These features allow developers to remove irrelevant characters, break text into searchable tokens, and standardize data for efficient querying. Unlike native SQL full-text search capabilities, Hibernate Search offers a more streamlined and scalable approach, making it ideal for applications requiring sophisticated search features.

Integrating Hibernate with Elasticsearch

The core of Bartosz’s presentation was a step-by-step guide to integrating Hibernate Search with Elasticsearch. He outlined five key steps: creating JPA entities, adding Hibernate Search dependencies, annotating entities for indexing, configuring fields for NoSQL storage, and performing initial indexing. By annotating entities with @Indexed, developers can create indexes in Elasticsearch at application startup. Fields are annotated as @FullTextField for tokenization and search, @KeywordField for sorting, or @GenericField for basic querying. Bartosz emphasized the importance of the @FullTextField, which enables advanced search capabilities like fuzzy matching and phrase queries. His live coding demo showcased how to set up a Docker Compose file with MySQL and Elasticsearch, configure the application, and index a bookstore’s data, demonstrating the ease of integrating these technologies.

Scalability and Synchronization Challenges

A significant advantage of using Elasticsearch with Hibernate Search is its scalability. Unlike Apache Lucene, which is limited to a single node and suited for smaller projects, Elasticsearch supports distributed data across multiple nodes, making it ideal for enterprise applications. However, Bartosz highlighted a key challenge: synchronization between SQL and NoSQL databases. Changes in the SQL database may not immediately reflect in Elasticsearch due to communication overhead. To address this, he introduced an experimental outbox polling coordination strategy, which uses additional SQL tables to maintain update order. While still in development, this feature promises to improve data consistency, a critical aspect for production environments.

Practical Applications and Benefits

Bartosz demonstrated practical applications of Hibernate Search through a bookstore example, where users could search for books by title, description, or reviews. His demo showed how to query Elasticsearch for terms like “Hibernate” or “programming,” retrieving relevant results ranked by relevance. Additionally, Hibernate Search supports advanced features like sorting by distance for geolocation-based queries and projections for retrieving partial documents, reducing reliance on the SQL database for certain operations. These capabilities make Hibernate Search a versatile tool for developers aiming to enhance search performance while maintaining their existing SQL infrastructure.

Links:

PostHeaderIcon [SpringIO2022] JobRunr: Simplifying Distributed Job Scheduling with Spring

At Spring I/O 2022 in Barcelona, Ronald Dehuysser introduced JobRunr, an open-source Java library designed to streamline distributed background job processing. His engaging session, blending practical insights with live coding, showcased how JobRunr empowers developers to transform Java 8 lambdas into scalable, fault-tolerant jobs without complex infrastructure. Tailored for businesses handling moderate data volumes, Ronald’s talk highlighted JobRunr’s seamless integration with Spring and its potential to revolutionize job scheduling.

The Genesis of JobRunr: Solving Real-World Challenges

Ronald, a contractor from Belgium, kicked off by sharing the origins of JobRunr, born from a challenging “greenfield” fintech project. Tasked with building an invoicing platform on Google Cloud, he encountered a microservice architecture plagued by issues: no retry mechanisms, poor monitoring, and lost invoices due to untracked failures. The project’s eight microservices led to code duplication, prompting Ronald to question the microservice hype and advocate for simpler, modular monoliths. Frustrated by the lack of developer-friendly, open-source job scheduling tools, he created JobRunr to address these gaps, emphasizing ease of use, existing infrastructure, and automatic retries.

JobRunr’s philosophy is rooted in simplicity and practicality. Unlike solutions requiring heavy infrastructure like Apache Kafka or vendor-specific cloud services, JobRunr leverages SQL or NoSQL databases for persistence, making it embeddable with a single JAR. Ronald stressed that most businesses don’t need to process terabytes daily like tech giants. Instead, JobRunr targets complex business processes with gigabytes of data, offering a plug-and-play solution with built-in monitoring and fault tolerance.

Core Features: From Lambdas to Distributed Jobs

The heart of JobRunr lies in its ability to convert Java 8 lambdas into distributed background jobs. Ronald demonstrated this with a Spring service example, where a static BackgroundJob.enqueue method schedules jobs without altering existing code. Jobs are serialized as JSON, stored in a database, and processed by BackgroundJobServer instances across JVMs, enabling horizontal scaling in Kubernetes. A dashboard provides real-time insights into job status, with automatic retries (up to 10 by default) using an exponential backoff policy to handle failures gracefully.

For scheduling flexibility, JobRunr supports immediate, delayed, or recurring jobs. Ronald showcased the schedule API for jobs running after a delay (e.g., 24 hours) and the scheduleRecurrently method for daily tasks, using a readable Cron class to simplify cron expressions. The dashboard allows manual triggering of recurring jobs for testing, enhancing developer control. To prevent duplicate processing, JobRunr offers mutex support, though advanced features like this are part of the paid Pro version, balancing open-source accessibility with sustainability.

Under the Hood: Bytecode Magic and Spring Native

Delving into JobRunr’s internals, Ronald revealed its use of ASM for bytecode manipulation, translating lambdas into executable jobs. While some criticized this as “black magic,” he countered with assurances of binary compatibility, backed by Oracle’s Java Language Specification and his participation in Oracle’s Quality Outreach Program. JobRunr’s compatibility spans Java 8 to 17, tested across JVMs using Testcontainers, ensuring robustness. The introduction of JobRequest and JobRequestHandler in version 4 further simplifies job definition, aligning with the command handler pattern for explicit job processing.

A highlight was JobRunr’s integration with Spring Native, enabling compilation to GraalVM native images for millisecond startup times and low memory usage. Ronald collaborated with the Spring team to ensure reflection compatibility, making JobRunr a natural fit for cloud-native deployments. The live coding demo, despite minor hiccups, showcased JobRunr’s ease of use: Ronald built an uptime monitoring service, scheduling recurring website checks with a few lines of code, monitored via the dashboard. This practicality resonated with attendees, who appreciated JobRunr’s developer-friendly approach.

Impact and Future: Empowering Developers

JobRunr’s adoption spans medical image processing, web crawling, and document generation, with 30,000 monthly Maven downloads. Ronald shared a compelling anecdote: a company reported a 20% developer productivity boost by using the dashboard’s requeue feature for first-line support, reducing interruptions. Looking ahead, JobRunr aims to enhance GraalVM support, add OpenID Connect for dashboard authentication, and incorporate community-driven features. The Pro version funds development, with 5% of profits supporting environmental causes like tree planting.

Ronald’s session underscored JobRunr’s mission to simplify distributed job scheduling, making it an invaluable tool for Spring developers tackling real-world business challenges with minimal overhead.

Links:

PostHeaderIcon A Decade of Devoxx FR and Java Evolution: A Detailed Retrospective and Forward-Looking Analysis


Introduction:

The Devoxx FR conference has served as a key barometer of the Java platform’s dynamic evolution over the past ten years. This period has been marked by numerous releases, including major advancements that have significantly reshaped how we architect, develop, and deploy Java applications. This presentation offers a detailed retrospective analysis of significant announcements and the substantial changes within Java, emphasizing the critical importance of embracing these enhancements to optimize our applications for performance, maintainability, and security. Beyond a surface-level examination of syntax and API modifications, this session provides a comprehensive rationale for migrating to newer Java versions, addressing the common concerns and challenges that often accompany such transitions with practical insights and actionable strategies.

1. A Detailed Look Back: Java’s Evolution Over the Past Decade

Jean-Michel “JM” Doudoux begins the session by establishing a parallel timeline of the ten-year history of the Devoxx FR conference and Java’s continuous development. He emphasizes the importance of understanding the reception and adoption rates of different Java versions to contextualize the current state of the Java ecosystem.

Java 8:

JM highlights Java 8 as a watershed release, noting its widespread adoption and the introduction of transformative features that fundamentally changed Java development. Key features include:

  • Lambda Expressions: Revolutionized functional programming in Java, enabling more concise and expressive code.
  • Stream API: Introduced a powerful and efficient way to process collections of data.
  • Method References: Simplified the syntax for referring to methods, further enhancing code readability.
  • New Date/Time API (java.time): Addressed the shortcomings of the old java.util.Date and java.util.Calendar APIs, providing a more robust and intuitive way to handle date and time.
  • Default Methods in Interfaces: Allowed adding new methods to interfaces without breaking backward compatibility.

Java 11:

JM points out the slower adoption rate of Java 11, despite being a Long-Term Support (LTS) release, which typically encourages enterprise adoption due to extended support guarantees. Notable features include:

  • HTTP Client API: Introduced a new and improved HTTP Client API, supporting HTTP/2 and WebSocket.

Java 17:

Characterized as a release that has garnered significant developer enthusiasm, building upon the foundation laid by previous versions and further refining the language.

Java 9:

Acknowledged as a disruptive release, primarily due to the introduction of the Java Platform Module System (JPMS), which brought modularity to Java. Doudoux discusses the profound impact of modularity on the Java ecosystem, affecting code organization, accessibility, and deployment.

Java 10, 12-16:

These releases are characterized as more transient, feature releases, with less widespread adoption compared to the LTS versions. However, they introduced valuable features such as:

  • Local Variable Type Inference (var): Simplified variable declaration.
  • Enhanced Switch Expressions: Improved the switch statement, making it more expressive and usable as an expression.

2. Navigating Migration: Java 17 and Strategic Considerations

The presentation transitions to a practical discussion on the complexities of migrating to newer Java versions, with a strong emphasis on the benefits and challenges of migrating to Java 17. Doudoux addresses the common obstacles developers encounter when advocating for migration within their organizations, particularly the challenge of securing buy-in from operations teams and management.

Strategies for Persuasion:

The speaker offers valuable strategies to help developers build a compelling case for migration, focusing on:

  • Highlighting Performance Improvements: Emphasizing the performance gains offered by newer Java versions.
  • Improved Security: Stressing the importance of security updates and enhancements.
  • Increased Developer Productivity: Showcasing how new language features can streamline development workflows.
  • Long-Term Maintainability: Arguing that staying on older versions increases technical debt and maintenance costs in the long run.

Migration Considerations:

While a detailed, step-by-step migration guide is beyond the scope of the session, Doudoux outlines the essential high-level considerations and key steps involved in the migration process, such as:

  • Dependency Analysis: Assessing compatibility with updated libraries and frameworks.
  • Testing: Thoroughly testing the application after migration.
  • Gradual Rollouts: Considering phased deployments to minimize risk.

3. The Future of Java: Trends and Directions

The session concludes with a concise yet insightful look at the future trajectory of the Java platform. This segment provides a glimpse into upcoming features, emerging trends, and the ongoing evolution of Java, ensuring the audience is aware of the continuous innovation within the Java ecosystem.

Summary:

This presentation provides a detailed and comprehensive overview of Java’s journey over the past decade, carefully contextualized within the parallel evolution of the Devoxx FR conference. It goes beyond a simple recitation of features, offering in-depth analysis of the impact of key advancements, practical guidance on navigating the complexities of Java migration, and a valuable perspective on the future of the platform.

PostHeaderIcon [DevoxxFR 2022] Exploiter facilement des fonctions natives avec le Projet Panama depuis Java

Lors de Devoxx France 2022, Brice Dutheil a présenté une conférence de 28 minutes sur le Projet Panama, une initiative visant à simplifier l’appel de fonctions natives depuis Java sans les complexités de JNI ou de bibliothèques tierces. Brice, contributeur actif à l’écosystème Java, a introduit l’API Foreign Function & Memory (JEP-419), montrant comment elle relie le monde géré de Java au code natif en C, Swift ou Rust. À travers des démonstrations de codage en direct, Brice a illustré le potentiel de Panama pour des intégrations natives fluides. Suivez Brice sur Twitter à twitter.com/Brice_Dutheil pour plus d’insights Java.

Simplifier l’intégration de code natif

Brice a débuté en expliquant la mission du Projet Panama : connecter l’environnement géré de Java, avec son garbage collector, au monde natif de C, Swift ou Rust, plus proche de la machine. Traditionnellement, JNI imposait des étapes laborieuses : écrire des classes wrapper, charger des bibliothèques et générer des headers lors des builds. Ces processus étaient sujets aux erreurs et chronophages. Des alternatives comme JNA et JNR amélioraient l’expérience développeur en générant des bindings au runtime, mais elles étaient plus lentes et moins sécurisées.

Lancé en 2014, le Projet Panama répond à ces défis avec trois composantes : les API vectorielles (non couvertes ici), les appels de fonctions étrangères et la gestion de la mémoire. Brice s’est concentré sur l’API Foreign Function & Memory (JEP-419), disponible en incubation dans JDK 18. Contrairement à JNI, Panama élimine les complexités du build et offre des performances proches du natif sur toutes les plateformes. Il introduit un modèle de sécurité robuste, limitant les opérations dangereuses et envisageant de restreindre JNI dans les futures versions de Java (par exemple, Java 25 pourrait exiger un flag pour activer JNI). Brice a souligné l’utilisation des method handles et des instructions d’invocation dynamique, inspirées des avancées du bytecode JVM, pour générer efficacement des instructions assembleur pour les appels natifs.

Démonstrations pratiques avec Panama

Brice a démontré les capacités de Panama via du codage en direct, commençant par un exemple simple appelant la fonction getpid de la bibliothèque standard C. À l’aide du SystemLinker, il a effectué une recherche de symbole pour localiser getpid, créé un method handle avec un descripteur de fonction définissant la signature (retournant un long Java), et l’a invoqué pour récupérer l’ID du processus. Ce processus a contourné les lourdeurs de JNI, nécessitant seulement quelques lignes de code Java. Brice a insisté sur l’activation de l’accès natif avec le flag –enable-native-access dans JDK 18, renforçant le modèle de sécurité de Panama en limitant l’accès à des modules spécifiques.

Il a ensuite présenté un exemple plus complexe avec la fonction crypto_box de la bibliothèque cryptographique Libsodium, portable sur des plateformes comme Android. Brice a alloué des segments de mémoire avec un ResourceScope et un NativeAllocator, garantissant la sécurité mémoire en libérant automatiquement les ressources après usage, contrairement à JNI qui dépend du garbage collector. Le ResourceScope prévient les fuites mémoire, une amélioration significative par rapport aux buffers natifs traditionnels. Brice a également abordé l’appel de code Swift via des interfaces compatibles C, démontrant la polyvalence de Panama.

Outils et potentiel futur

Brice a introduit jextract, un outil de Panama qui génère des mappings Java à partir de headers C/C++, simplifiant l’intégration de bibliothèques comme Blake3, une fonction de hachage performante écrite en Rust. Dans une démo, il a montré comment jextract créait des bindings compatibles Panama pour les structures de données et fonctions de Blake3, permettant aux développeurs Java de tirer parti des performances natives sans bindings manuels. Malgré quelques accrocs, la démo a souligné le potentiel de Panama pour des intégrations natives transparentes.

Brice a conclu en soulignant les avantages de Panama : simplicité, rapidité, compatibilité multiplateforme et sécurité mémoire renforcée. Il a noté son évolution continue, avec JEP-419 en incubation dans JDK 18 et une deuxième preview prévue pour JDK 19. Pour les développeurs d’applications desktop ou de systèmes critiques, Panama offre une solution puissante pour exploiter des fonctions spécifiques aux OS ou des bibliothèques optimisées comme Libsodium. Brice a encouragé le public à expérimenter Panama et à poser des questions, renforçant son engagement via Twitter.

PostHeaderIcon [DevoxxPL2019] GraphQL in the Java Ecosystem: A Comprehensive Exploration

Lecturer

Vladimir Dejanović is a seasoned software professional with over a decade of experience in the IT industry, having contributed to diverse projects since 2006. As Senior Director of B2C Technology at PVH, a fashion technology firm overseeing brands like Tommy Hilfiger and Calvin Klein, he focuses on scalable systems and innovative solutions. Beyond his corporate role, Vladimir founded and leads the Amsterdam Java User Group, fostering community engagement in Java technologies. He is recognized as an Oracle Code One Star and Java Rockstar, frequently delivering presentations at international conferences on topics like GraphQL and Java development.

Abstract

This article delves into the intricacies of GraphQL as applied within Java environments, examining its specification, implementation strategies, and practical applications through code demonstrations. It analyzes the advantages of GraphQL over traditional REST APIs, such as enhanced query flexibility and schema validation, while addressing potential pitfalls like cyclic dependencies and security concerns. Drawing from real-world examples, the discussion highlights methodologies for schema design, resolver integration, and performance optimization, underscoring GraphQL’s role in modern API development and its implications for system architecture.

Understanding GraphQL: Beyond the Basics

GraphQL emerges as a pivotal specification in API design, originating from Facebook in 2015 to address inefficiencies in data fetching encountered during mobile application development. Unlike conventional REST APIs, which often result in over-fetching or under-fetching of data, GraphQL empowers clients to request precisely the information needed, thereby optimizing network usage and enhancing performance. The specification defines a query language that allows for declarative data retrieval, where clients specify the structure of the response, aligning closely with application requirements.

At its core, GraphQL is not a full-fledged framework but a set of guidelines that various languages implement differently. In Java, implementations like GraphQL Java provide the engine for processing queries, while tools such as GraphQL Java Kickstarters facilitate integration with existing infrastructures, such as Spring Boot. This flexibility means developers must be cognizant of implementation-specific nuances, including coverage of the specification and additional features not mandated by the core rules. For instance, while the specification mandates schema validation, implementations may vary in handling extensions like custom scalars or error propagation.

The schema definition language (SDL) stands out as GraphQL’s most potent feature, surpassing alternatives like OpenAPI in expressiveness. It requires a mandatory schema that describes types, fields, and relationships, ensuring both client and server adhere to a contract. Upon connection, the server transmits the schema, enabling clients to validate requests locally before transmission, which reduces invalid traffic and conserves resources. This schema-first approach, preferred for its mockability, allows teams to prototype APIs independently: backend developers define the schema, while frontend teams use mocks to simulate responses.

Consider a practical scenario involving a conference application with entities like attendees, speakers, and talks. The schema might define types as follows:

type Attendee {
  id: ID!
  name: String
}

type Speaker {
  id: ID!
  name: String
  twitter: String
}

type Talk {
  id: ID!
  title: String
  description: String
  speakers: [Speaker]
}

Here, the exclamation mark denotes mandatory fields, and arrays indicate relationships. This structure not only documents the API but also enforces consistency, preventing outdated documentation—a common issue in REST environments.

Implementing Queries and Resolvers in Java

Transitioning to code, integrating GraphQL in Java involves wiring the schema to business logic. Using Spring Boot and GraphQL Java, one initializes a servlet mapped to “/graphql”, parsing the schema and registering resolvers. Resolvers act as the bridge, implementing interfaces like GraphQLQueryResolver for read operations.

For the conference example, a Query class might look like this:

@Component
public class Query implements GraphQLQueryResolver {
  private final TalkService talkService;
  private final SpeakerService speakerService;
  private final AttendeeService attendeeService;

  @RequiredArgsConstructor
  public Query(TalkService talkService, SpeakerService speakerService, AttendeeService attendeeService) {
    this.talkService = talkService;
    this.speakerService = speakerService;
    this.attendeeService = attendeeService;
  }

  public List<Talk> allTalks() {
    return talkService.findAll();
  }

  public List<Speaker> allSpeakers() {
    return speakerService.findAll();
  }

  public List<Attendee> allAttendees() {
    return attendeeService.findAll();
  }
}

This setup enables queries like fetching all talks with specific fields:

query {
  allTalks {
    id
    title
    description
    speakers {
      name
      twitter
    }
  }
}

The response mirrors the query structure in JSON, promoting predictability. Clients can alias fields (e.g., renaming “title” to “myTitle”) or conditionally include them using directives like @include(if: $variable), where variables are passed separately for dynamic behavior.

Resolvers for relationships, such as linking talks to speakers, extend GraphQLResolver:

@Component
public class TalkResolver implements GraphQLResolver<Talk> {
  private final SpeakerService speakerService;

  @RequiredArgsConstructor
  public TalkResolver(SpeakerService speakerService) {
    this.speakerService = speakerService;
  }

  public List<Speaker> speakers(Talk talk) {
    return speakerService.findAllSpeakersForTalk(talk);
  }
}

This modular approach allows for granular control, but it introduces risks like cyclic queries if bidirectional links (e.g., speakers to talks) are added without safeguards. Such cycles can lead to infinite loops, necessitating depth limits during validation.

Mutations: Enabling Data Modification

While queries handle reads, mutations facilitate writes, mirroring CRUD operations but with GraphQL’s precision. Defined similarly in the schema:

type Mutation {
  addTalk(input: TalkInput!): Talk
}

Mutations require explicit input types to encapsulate parameters, ensuring type safety. In Java, a Mutation resolver implements GraphQLMutationResolver:

@Component
public class Mutation implements GraphQLMutationResolver {
  private final TalkService talkService;

  @RequiredArgsConstructor
  public Mutation(TalkService talkService) {
    this.talkService = talkService;
  }

  public Talk addTalk(TalkInput input) {
    // Logic to create and persist talk
    return talkService.save(input.toTalk());
  }
}

This method processes inputs, validates them, and returns the updated entity. Unlike queries, which can execute in parallel for optimization, mutations are sequential to maintain data integrity. Errors in mutations propagate similarly to queries, with customizable handlers to continue processing or halt execution.

The implications are profound: mutations reduce boilerplate compared to REST’s multiple endpoints, centralizing logic while allowing clients to request related data in the same response, such as fetching the newly added talk’s speakers.

Subscriptions: Real-Time Data Streams

Subscriptions introduce reactive capabilities, enabling server-push updates over WebSockets. The schema defines:

type Subscription {
  scores(title: String!): Score
}

type Score {
  title: String
  score: Int
}

In Java, using Reactor for reactivity:

@Component
public class Subscription implements GraphQLSubscriptionResolver {
  public Publisher<Score> scores(String title) {
    return Flux.interval(Duration.ofSeconds(2))
      .map(i -> Score.builder()
        .title(title)
        .score(ThreadLocalRandom.current().nextInt(1, 6))
        .build());
  }
}

This generates scores every two seconds, demonstrating backpressure handling to prevent client overload. Subscriptions transform static APIs into dynamic ones, ideal for live updates like conference feedback, though implementation varies—GraphQL Java uses WebSockets, but the specification leaves transport open.

Security and Performance Considerations

Security in GraphQL demands vigilance, as the schema’s public nature exposes potential attack vectors. Authentication and authorization occur via custom contexts:

public class MyGraphQLContext extends GraphQLContext {
  private final User user;

  public MyGraphQLContext(User user) {
    this.user = user;
  }
}

Resolvers access this context via DataFetchingEnvironment to enforce roles. For schema protection, directives or instrumentation filter visibility, though non-standard approaches risk interoperability.

Performance pitfalls include N+1 queries, mitigated by batching or caching in services. Instrumentation traces execution:

public class TracingInstrumentation extends SimpleInstrumentation {
  @Override
  public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {
    // Start timer, log query
    return super.beginExecution(parameters);
  }
}

Query complexity analysis during validation prevents denial-of-service attacks by capping depth or computational cost.

Schema management in large systems involves stitching or extensions to avoid monolithic files:

extend type Speaker {
  twitter: String
}

This federates schemas across services, though conflicts require governance.

Implications for Modern Development

GraphQL’s client-centric model shifts power from servers, fostering agile development but requiring robust safeguards. In Java, its integration with Spring Boot streamlines adoption, yet demands awareness of implementation variances. By enabling precise data fetching and real-time interactions, it addresses REST’s limitations, promoting efficient, scalable architectures. Future specification enhancements, like improved subscription standards, promise broader applicability.

Links:

PostHeaderIcon [DevoxxFR 2018] Java in Docker: Best Practices for Production

The practice of running Java applications within Docker containers has become widely adopted in modern software deployment, yet it is not devoid of potential challenges, particularly when transitioning to production environments. Charles Sabourdin, a freelance architect, and Jean-Christophe Sirot, an engineer at Docker, collaborated at DevoxxFR2018 to share their valuable experiences and disseminate best practices for optimizing Java applications inside Docker containers. Their insightful talk directly addressed common and often frustrating issues, such as containers crashing unexpectedly, applications consuming excessive RAM leading to node instability, and encountering CPU throttling. They offered practical solutions and configurations aimed at ensuring smoother and more reliable production deployments for Java workloads.

The presenters initiated their session with a touch of humor, explaining why operations teams might exhibit a degree of apprehension when tasked with deploying a containerized Java application into a production setting. It’s a common scenario: containers that perform flawlessly on a developer’s local machine can begin to behave erratically or fail outright in production. This discrepancy often stems from a fundamental misunderstanding of how the Java Virtual Machine (JVM) interacts with the resource limits imposed by the container’s control groups (cgroups). Several key problems frequently surface in this context. Perhaps the most common is memory mismanagement; the JVM, particularly older versions, might not be inherently aware of the memory limits defined for its container by the cgroup. This lack of awareness can lead the JVM to attempt to allocate and use more memory than has been allocated to the container by the orchestrator or runtime. Such overconsumption inevitably results in the container being abruptly terminated by the operating system’s Out-Of-Memory (OOM) killer, a situation that can be difficult to diagnose without understanding this interaction.

Similarly, CPU resource allocation can present challenges. The JVM might not accurately perceive the CPU resources available to it within the container, such as CPU shares or quotas defined by cgroups. This can lead to suboptimal decisions in sizing internal thread pools (like the common ForkJoinPool or garbage collection threads) or can cause the application to experience unexpected CPU throttling, impacting performance. Another frequent issue is Docker image bloat. Overly large Docker images not only increase deployment times across the infrastructure but also expand the potential attack surface by including unnecessary libraries or tools, thereby posing security vulnerabilities. The talk aimed to equip developers and operations personnel with the knowledge to anticipate and mitigate these common pitfalls. During the presentation, a demonstration application, humorously named “ressources-munger,” was used to simulate these problems, clearly showing how an application could consume excessive memory leading to an OOM kill by Docker, or how it might trigger excessive swapping if not configured correctly, severely degrading performance.

JVM Memory Management and CPU Considerations within Containers

A significant portion of the discussion was dedicated to the intricacies of JVM memory management within the containerized environment. Charles and Jean-Christophe elaborated that older JVM versions, specifically those prior to Java 8 update 131 and Java 9, were not inherently “cgroup-aware”. This lack of awareness meant that the JVM’s default heap sizing heuristics—for example, typically allocating up to one-quarter of the physical host’s memory for the heap—would be based on the total resources of the host machine rather than the specific limits imposed on the container by its cgroup. This behavior is a primary contributor to unexpected OOM kills when the container’s actual memory limit is much lower than what the JVM assumes based on the host.

Several best practices were shared to address these memory-related issues effectively. The foremost recommendation is to use cgroup-aware JVM versions. Modern Java releases, particularly Java 8 update 191 and later, and Java 10 and newer, incorporate significantly improved cgroup awareness. For older Java 8 updates (specifically 8u131 to 8u190), experimental flags such as -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap can be employed to enable the JVM to better respect container memory limits. In Java 10 and subsequent versions, this behavior became standard and often requires no special flags. However, even with cgroup-aware JVMs, explicitly setting the heap size using parameters like -Xms for the initial heap size and -Xmx for the maximum heap size is frequently a recommended practice for predictability and control. Newer JVMs also offer options like -XX:MaxRAMPercentage, allowing for more dynamic heap sizing relative to the container’s allocated memory. It’s crucial to understand that the JVM’s total memory footprint extends beyond just the heap; it also requires memory for metaspace (which replaced PermGen in Java 8+), thread stacks, native libraries, and direct memory buffers. Therefore, when allocating memory to a container, it is essential to account for this total footprint, not merely the -Xmx value. A common guideline suggests that the Java heap might constitute around 50-75% of the total memory allocated to the container, with the remainder reserved for these other essential JVM components and any other processes running within the container. Tuning metaspace parameters, such as -XX:MetaspaceSize and -XX:MaxMetaspaceSize, can also prevent excessive native memory consumption, particularly in applications that dynamically load many classes.

Regarding CPU resources, the presenters noted that the JVM’s perception of available processors is also influenced by its cgroup awareness. In environments where CPU resources are constrained, using flags like -XX:ActiveProcessorCount can be beneficial to explicitly inform the JVM about the number of CPUs it should consider for sizing its internal thread pools, such as the common ForkJoinPool or the threads used for garbage collection. Optimizing the Docker image itself is another critical aspect of preparing Java applications for production. This involves choosing a minimal base image, such as alpine-jre, distroless, or official “slim” JRE images, instead of a full operating system distribution, to reduce the image size and potential attack surface. Utilizing multi-stage builds in the Dockerfile is a highly recommended technique; this allows developers to use a larger image containing build tools like Maven or Gradle and a full JDK in an initial stage, and then copy only the necessary application artifacts (like the JAR file) and a minimal JRE into a final, much smaller runtime image. Furthermore, being mindful of Docker image layering by combining related commands in the Dockerfile where possible can help reduce the number of layers and optimize image size. For applications on Java 9 and later, tools like jlink can be used to create custom, minimal JVM runtimes that include only the Java modules specifically required by the application, further reducing the image footprint. The session strongly emphasized that a collaborative approach between development and operations teams, combined with a thorough understanding of both JVM internals and Docker containerization principles, is paramount for successfully and reliably running Java applications in production environments.

Links:

Hashtags: #Java #Docker #JVM #Containerization #DevOps #Performance #MemoryManagement #DevoxxFR2018 #CharlesSabourdin #JeanChristopheSirot #BestPractices #ProductionReady #CloudNative

PostHeaderIcon [DevoxxUS2017] 55 New Features in JDK 9: A Comprehensive Overview

At DevoxxUS2017, Simon Ritter, Deputy CTO at Azul Systems, delivered a detailed exploration of the 55 new features in JDK 9, with a particular focus on modularity through Project Jigsaw. Simon, a veteran Java evangelist, provided a whirlwind tour of the enhancements, categorizing them into features, standards, JVM internals, specialized updates, and housekeeping changes. His presentation equipped developers with the knowledge to leverage JDK 9’s advancements effectively. This post examines the key themes of Simon’s talk, highlighting how these features enhance Java’s flexibility, performance, and maintainability.

Modularity and Project Jigsaw

The cornerstone of JDK 9 is Project Jigsaw, which introduces modularity to the Java platform. Simon explained that the traditional rt.jar file, containing over 4,500 classes, has been replaced with 94 modular components in the jmods directory. This restructuring encapsulates private APIs, such as sun.misc.Unsafe, to improve security and maintainability, though it poses compatibility challenges for libraries relying on these APIs. To mitigate this, Simon highlighted options like the --add-exports and --add-opens flags, as well as a “big kill switch” (--permit-illegal-access) to disable modularity for legacy applications. The jlink tool further enhances modularity by creating custom runtimes with only the necessary modules, optimizing deployment for specific applications.

Enhanced APIs and Developer Productivity

JDK 9 introduces several API improvements to streamline development. Simon showcased factory methods for collections, allowing developers to create immutable collections with concise syntax, such as List.of() or Set.of(). The Streams API has been enhanced with methods like takeWhile, dropWhile, and ofNullable, improving expressiveness in data processing. Additionally, the introduction of jshell, an interactive REPL, enables rapid prototyping and experimentation. These enhancements reduce boilerplate code and enhance developer productivity, making Java more intuitive and efficient for modern application development.

JVM Internals and Performance

Simon delved into JVM enhancements, including improvements to the G1 garbage collector, which is now the default in JDK 9. The G1 collector offers better performance for large heaps, addressing limitations of the Concurrent Mark Sweep collector. Other internal improvements include a new process API for accessing operating system process details and a directive file for controlling JIT compiler behavior. These changes enhance runtime efficiency and provide developers with greater control over JVM performance, ensuring Java remains competitive for high-performance applications.

Housekeeping and Deprecations

JDK 9 includes significant housekeeping changes to streamline the platform. Simon highlighted the new version string format, adopting semantic versioning (major.minor.security.patch) for clearer identification. The directory structure has been flattened, eliminating the JRE subdirectory and tools.jar, with configuration files centralized in the conf directory. Deprecated APIs, such as the applet API and certain garbage collection options, have been removed to reduce maintenance overhead. These changes simplify the JDK’s structure, improving maintainability while requiring developers to test applications for compatibility.

Standards and Specialized Features

Simon also covered updates to standards and specialized features. The HTTP/2 client, introduced as an incubator module, allows developers to test and provide feedback before it becomes standard. Other standards updates include support for Unicode 8.0 and the deprecation of SHA-1 certificates for enhanced security. Specialized features, such as the annotations pipeline and parser API, improve the handling of complex annotations and programmatic interactions with the compiler. These updates ensure Java aligns with modern standards while offering flexibility for specialized use cases.

Links: