Recent Posts
Archives

Posts Tagged ‘Java’

PostHeaderIcon [DevoxxBE2024] Java Language Futures by Gavin Bierman

Gavin Bierman, from Oracle’s Java Platform Group, captivated attendees at Devoxx Belgium 2024 with a forward-looking talk on Java’s evolution under Project Amber. Focusing on productivity-oriented language features, Gavin outlined recent additions like records, sealed classes, and pattern matching, while previewing upcoming enhancements like simplified main methods and flexible constructor bodies. His session illuminated Java’s design philosophy—prioritizing readability, explicit programmer intent, and compatibility—while showcasing how these features enable modern, data-oriented programming paradigms suited for today’s microservices architectures.

Project Amber’s Mission: Productivity and Intent

Gavin introduced Project Amber as a vehicle for delivering smaller, productivity-focused Java features, leveraging the six-month JDK release cadence to preview and finalize enhancements. Unlike superficial syntax changes, Amber emphasizes exposing programmer intent to improve code readability and reduce bugs. Compatibility is paramount, with breaking changes minimized, as Java evolves to address modern challenges distinct from its 1995 origins. Gavin highlighted how features like records and sealed classes make intent explicit, enabling the compiler to enforce constraints and provide better error checking, aligning with the needs of contemporary applications.

Records: Simplifying Data Carriers

Records, introduced to streamline data carrier classes, were a key focus. Gavin demonstrated how a Point class with two integers requires verbose boilerplate (constructors, getters, equals, hashCode) that obscures intent. Records (record Point(int x, int y)) eliminate this by auto-generating a canonical constructor, accessor methods, and value-based equality, ensuring immutability and transparency. This explicitness allows the compiler to enforce a contract: constructing a record from its components yields an equal instance. Records also support deserialization via the canonical constructor, ensuring domain-specific constraints, making them safer than traditional classes.

Sealed Classes and Pattern Matching

Sealed classes, shipped in JDK 17, allow developers to restrict class hierarchies explicitly. Gavin showed a Shape interface sealed to permit only Circle and Rectangle implementations, preventing unintended subclasses at compile or runtime. This clarity enhances library design by defining precise interfaces. Pattern matching, enhanced in JDK 21, further refines this by enabling type patterns and record patterns in instanceof and switch statements. For example, a switch over a sealed Shape interface requires exhaustive cases, eliminating default clauses and reducing errors. Nested record patterns allow sophisticated data queries, handling nulls safely without exceptions.

Data-Oriented Programming with Amber Features

Gavin illustrated how records, sealed classes, and pattern matching combine to support data-oriented programming, ideal for microservices exchanging pure data. He reimagined the Future class’s get method, traditionally complex due to multiple control paths (success, failure, timeout, interruption). By modeling the return type as a sealed AsyncReturn interface with four record implementations (Success, Failure, Timeout, Interrupted), and using pattern matching in a switch, developers handle all cases uniformly. This approach simplifies control flow, ensures exhaustiveness, and leverages Java’s type safety, contrasting with error-prone exception handling in traditional designs.

Future Features: Simplifying Java for All

Looking ahead, Gavin previewed features in JDK 23 and beyond. Simplified main methods allow beginners to write void main() without boilerplate, reducing cognitive load while maintaining full Java compatibility. The with expression for records enables concise updates (e.g., doubling a component) without redundant constructor calls, preserving domain constraints. Flexible constructor bodies (JEP 482) relax top-down initialization, allowing pre-super call logic to validate inputs, addressing issues like premature field access in subclass constructors. Upcoming enhancements include patterns for arbitrary classes, safe template programming, and array pattern matching, promising further productivity gains.

Links:

PostHeaderIcon [DevoxxBE2024] Project Panama in Action: Building a File System by David Vlijmincx

At Devoxx Belgium 2024, David Vlijmincx delivered an engaging session on Project Panama, demonstrating its power by building a custom file system in Java. This practical, hands-on talk showcased how Project Panama simplifies integrating C libraries into Java applications, replacing the cumbersome JNI with a more developer-friendly approach. By leveraging Fuse, virtual threads, and Panama’s memory management capabilities, David walked attendees through creating a functional file system, highlighting real-world applications and performance benefits. His talk emphasized the ease of integrating C libraries and the potential to build high-performance, innovative solutions.

Why Project Panama Matters

David began by addressing the challenges of JNI, which many developers find frustrating due to its complexity. Project Panama, part of OpenJDK, offers a modern alternative for interoperating with native C libraries. With a vast ecosystem of specialized C libraries—such as io_uring for asynchronous file operations or libraries for AI and keyboard communication—Panama enables Java developers to access functionality unavailable in pure Java. David demonstrated this by comparing file reading performance: using io_uring with Panama, he read files faster than Java’s standard APIs (e.g., BufferedReader or Channels) in just two nights of work, showcasing Panama’s potential for performance-critical applications.

Building a File System with Fuse

The core of David’s demo was integrating the Fuse (Filesystem in Userspace) library to create a custom file system. Fuse acts as a middle layer, intercepting commands like ls from the terminal and passing them to a Java application via Panama. David explained how Fuse provides a C struct that Java developers can populate with pointers to Java methods, enabling seamless communication between C and Java. This struct, filled with method pointers, is mounted to a directory (e.g., ~/test), allowing the Java application to handle file system operations transparently to the user, who sees only the terminal output.

Memory Management with Arenas

A key component of Panama is its memory management via arenas, which David used to allocate memory for passing strings to Fuse. He demonstrated using Arena.ofShared(), which allows memory sharing across threads and explicit lifetime control via try-with-resources. Other arena types, like Arena.ofConfined() (single-threaded) or Arena.global() (unbounded lifetime), were mentioned for context. David allocated a memory segment to store pointers to a string array (e.g., ["-f", "-d", "~/test"]) and used Arena.allocateFrom() to create C-compatible strings. This ensured safe memory handling when interacting with Fuse, preventing leaks and simplifying resource management.

Downcalls and Upcalls: Bridging Java and C

David detailed the process of making downcalls (Java to C) and upcalls (C to Java). For downcalls, he created a function descriptor mirroring the C method’s signature (e.g., fuse_main_real, returning an int and taking parameters like string arrays and structs). Using Linker.nativeLinker(), he generated a platform-specific linker to invoke the C method. For upcalls, he recreated Fuse’s struct in Java using MemoryLayout.structLayout, populating it with pointers to Java methods like getattr. Tools like JExtract simplified this by generating bindings automatically, reducing boilerplate code. David showed how JExtract creates Java classes from C headers, though it requires an additional abstraction layer for user-friendly APIs.

Implementing File System Operations

David implemented two file system operations: reading files and creating directories. For reading, he extracted the file path from a memory segment using MemorySegment.getString(), checked if it was a valid file, and copied file contents into a buffer with MemorySegment.reinterpret() to handle size constraints. For directory creation, he added paths to a map, demonstrating simplicity. Running the application mounted the file system to ~/test, where commands like mkdir and echo worked seamlessly, with Fuse calling Java methods via upcalls. David unmounted the file system, showing its clean integration. Performance tips included reusing method handles and memory segments to avoid overhead, emphasizing careful memory management.

Links:

PostHeaderIcon [SpringIO2024] Serverless Java with Spring by Maximilian Schellhorn & Dennis Kieselhorst @ Spring I/O 2024

Serverless computing has transformed application development by abstracting infrastructure management, offering fine-grained scaling, and billing only for execution time. At Spring I/O 2024 in Barcelona, Maximilian Schellhorn and Dennis Kieselhorst, AWS Solutions Architects, shared their expertise on building serverless Java applications with Spring. Their session explored running existing Spring Boot applications in serverless environments and developing event-driven applications using Spring Cloud Function, with a focus on performance optimizations and practical tooling.

The Serverless Paradigm

Maximilian began by contrasting traditional containerized applications with serverless architectures. Containers, while resource-efficient, require developers to manage orchestration, networking, and scaling. Serverless computing, exemplified by AWS Lambda, eliminates these responsibilities, allowing developers to focus on code. Maximilian highlighted four key promises: reduced operational overhead, automatic granular scaling, pay-per-use billing, and high availability. Unlike containers, which remain active and incur costs even when idle, serverless functions scale to zero, executing only in response to events like API requests or queue messages, optimizing cost and resource utilization.

Spring Cloud Function for Event-Driven Development

Spring Cloud Function simplifies serverless development by enabling developers to write event-driven applications as Java functions. Maximilian demonstrated how it leverages Spring Boot’s familiar features—autoconfiguration, dependency injection, and testing—while abstracting cloud-specific details. Functions receive event payloads (e.g., JSON from API Gateway or Kafka) and can convert them into POJOs, streamlining business logic implementation. The framework’s generic invoker supports function routing, allowing multiple functions within a single codebase, and enables local testing via HTTP endpoints. This portability ensures applications can target various serverless platforms without vendor lock-in, enhancing flexibility.

Adapting Existing Spring Applications

For teams with existing Spring Boot applications, Dennis introduced the AWS Serverless Java Container, an open-source library acting as an adapter to translate serverless events into Java Servlet requests. This allows REST controllers to function unchanged in a serverless environment. Version 2.0.2, released during the conference, supports Spring Boot 3 and integrates with Spring Cloud Function. Dennis emphasized its ease of use: add the library, configure a handler, and deploy. While this approach incurs some overhead compared to native functions, it enables rapid migration of legacy applications, preserving existing investments without requiring extensive rewrites.

Optimizing Performance with SnapStart and GraalVM

Performance, particularly cold start times, is a critical concern in serverless Java applications. Dennis addressed this by detailing AWS Lambda SnapStart, which snapshots the initialized JVM and micro-VM, reducing startup times by up to 80% without additional costs. SnapStart, integrated with Spring Boot 3.2’s CRaC (Coordinated Restore at Checkpoint) support, manages initialization hooks to handle resources like database connections. For further optimization, Maximilian discussed GraalVM native images, which compile Java code into binaries for faster startups and lower memory usage. However, GraalVM’s complexity and framework limitations make SnapStart the preferred starting point, with GraalVM reserved for extreme performance needs.

Practical Considerations and Tooling

Maximilian and Dennis stressed practical considerations, such as database connection management and observability. Serverless scaling can overwhelm traditional databases, necessitating connection pooling adjustments or proxies like AWS RDS Proxy. Observability in Lambda relies on a push model, integrating with tools like CloudWatch, X-Ray, or OpenTelemetry, though additional layers may impact performance. To aid adoption, they offered a Lambda Workshop and a Serverless Java Replatforming Guide, providing hands-on learning and written guidance. These resources, accessible via AWS accounts, empower developers to experiment and apply serverless principles effectively.

Links:

PostHeaderIcon [DevoxxUK2024] Enter The Parallel Universe of the Vector API by Simon Ritter

Simon Ritter, Deputy CTO at Azul Systems, delivered a captivating session at DevoxxUK2024, exploring the transformative potential of Java’s Vector API. This innovative API, introduced as an incubator module in JDK 16 and now in its eighth iteration in JDK 23, empowers developers to harness Single Instruction Multiple Data (SIMD) instructions for parallel processing. By leveraging Advanced Vector Extensions (AVX) in modern processors, the Vector API enables efficient execution of numerically intensive operations, significantly boosting application performance. Simon’s talk navigates the intricacies of vector computations, contrasts them with traditional concurrency models, and demonstrates practical applications, offering developers a powerful tool to optimize Java applications.

Understanding Concurrency and Parallelism

Simon begins by clarifying the distinction between concurrency and parallelism, a common source of confusion. Concurrency involves tasks that overlap in execution time but may not run simultaneously, as the operating system may time-share a single CPU. Parallelism, however, ensures tasks execute simultaneously, leveraging multiple CPUs or cores. For instance, two users editing documents on separate machines achieve parallelism, while a single-core CPU running multiple tasks creates the illusion of parallelism through time-sharing. Java’s threading model, introduced in JDK 1.0, facilitates concurrency via the Thread class, but coordinating data sharing across threads remains challenging. Simon highlights how Java evolved with the concurrency utilities in JDK 5, the Fork/Join framework in JDK 7, and parallel streams in JDK 8, each simplifying concurrent programming while introducing trade-offs, such as non-deterministic results in parallel streams.

The Essence of Vector Processing

The Vector API, distinct from the legacy java.util.Vector class, enables true parallel processing within a single execution unit using SIMD instructions. Simon explains that vectors in mathematics represent sets of values, unlike scalars, and the Vector API applies this concept by storing multiple values in wide registers (e.g., 256-bit AVX2 registers). These registers, divided into lanes (e.g., eight 32-bit integers), allow a single operation, such as adding a constant, to process all lanes in one clock cycle. This contrasts with iterative loops, which process elements sequentially. Historical context reveals SIMD’s roots in 1960s supercomputers like the ILLIAC IV and Cray-1, with modern implementations in Intel’s MMX, SSE, and AVX instructions, culminating in AVX-512 with 512-bit registers. The Vector API abstracts these complexities, enabling developers to write cross-platform code without targeting specific microarchitectures.

Leveraging the Vector API

Simon illustrates the Vector API’s practical application through its core components: Vector, VectorSpecies, and VectorShape. The Vector class, parameterized by type (e.g., Integer), supports operations like addition and multiplication across all lanes. Subclasses like IntVector handle primitive types, offering methods like fromArray to populate vectors from arrays. VectorShape defines register sizes (64 to 512 bits or S_MAX for the largest available), ensuring portability across architectures like Intel and ARM. VectorSpecies combines type and shape, specifying, for example, an IntVector with eight lanes in a 256-bit register. Simon demonstrates a loop processing a million-element array, using VectorSpecies to calculate iterations based on lane count, and employs VectorMask to handle partial arrays, ensuring no side effects from unused lanes. This approach optimizes performance for numerically intensive tasks, such as matrix computations or data transformations.

Performance Insights and Trade-offs

The Vector API’s performance benefits shine in specific scenarios, particularly when autovectorization by the JIT compiler is insufficient. Simon references benchmarks from Tomas Zezula, showing that explicit Vector API usage outperforms autovectorization for small arrays (e.g., 64 elements) due to better register utilization. However, for larger arrays (e.g., 2 million elements), memory access latency—100+ cycles for RAM versus 3-5 for L1 cache—diminishes gains. Conditional operations, like adding only even-valued elements, further highlight the API’s value, as the C2 JIT compiler often fails to autovectorize such cases. Azul’s Falcon JIT compiler, based on LLVM, improves autovectorization, but explicit Vector API usage remains superior for complex operations. Simon emphasizes that while the API offers significant flexibility through masks and shuffles, its benefits wane with large datasets due to memory bottlenecks.

Links:

PostHeaderIcon [SpringIO2024] Continuations: The Magic Behind Virtual Threads in Java by Balkrishna Rawool @ Spring I/O 2024

At Spring I/O 2024 in Barcelona, Balkrishna Rawool, a software engineer at ING Bank, captivated attendees with an in-depth exploration of continuations, the underlying mechanism powering Java’s virtual threads. Introduced as a final feature in Java 21 under Project Loom, virtual threads promise unprecedented scalability for Java applications. Balkrishna’s session demystified how continuations enable this scalability by allowing programs to pause and resume execution, offering a deep dive into their mechanics and practical applications.

Understanding Virtual Threads

Virtual threads, a cornerstone of Project Loom, are lightweight user threads designed to enhance scalability in Java applications. Unlike platform threads, which map directly to operating system threads and are resource-intensive, virtual threads require minimal memory, enabling developers to create millions without significant overhead. Balkrishna illustrated this by comparing platform threads, often pooled due to their cost, to virtual threads, which are created and discarded as needed, avoiding pooling anti-patterns. He emphasized that virtual threads rely on platform threads—termed carrier threads—for execution, with a scheduler mounting and unmounting them dynamically. This mechanism ensures efficient CPU utilization, particularly in I/O-bound applications where threads spend considerable time waiting, thus boosting scalability.

The Power of Continuations

Continuations, the core focus of Balkrishna’s talk, are objects that represent a program’s current state or the “rest” of its computation. They allow developers to pause a program’s execution and resume it later, a capability critical to virtual threads’ efficiency. Using Java’s Continuation API, Balkrishna demonstrated how continuations pause execution via the yield method, transferring control back to the caller, and resume via the run method. He showcased this with a simple example where a continuation printed values, paused at specific points, and resumed, highlighting the manipulation of the call stack to achieve this control transfer. Although the Continuation API is not intended for direct application use, understanding it provides insight into virtual threads’ behavior and scalability.

Building a Generator with Continuations

To illustrate continuations’ versatility, Balkrishna implemented a generator—a data structure yielding values lazily—using only the Continuation API, eschewing Java’s streams or iterators. Generators are ideal for resource-intensive computations, producing values only when needed. In his demo, Balkrishna created a generator yielding strings (“a,” “b,” “c”) by defining a Source object to handle value yields and pauses via continuations. The generator paused after each yield, allowing consumers to iterate over values in a loop, demonstrating how continuations enable flexible control flow beyond virtual threads, applicable to constructs like coroutines or exception handling.

Crafting a Simple Virtual Thread

In the session’s climax, Balkrishna guided attendees through implementing a simplified virtual thread class using continuations. The custom virtual thread paused execution during blocking operations, freeing platform threads, and supported a many-to-many relationship with carrier threads. He introduced a scheduler to manage virtual threads on a fixed pool of platform threads, using a queue for first-in-first-out scheduling. A demo with thousands of virtual threads, each simulating blocking calls, outperformed an equivalent platform-thread implementation, underscoring virtual threads’ scalability. By leveraging scoped values and timers, Balkrishna ensured accurate thread identification and resumption, providing a clear, hands-on understanding of virtual threads’ mechanics.

Links:

PostHeaderIcon [DevoxxUK2024] Project Leyden: Capturing Lightning in a Bottle by Per Minborg

Per Minborg, a seasoned member of Oracle’s Core Library team, delivered an insightful session at DevoxxUK2024, unveiling the ambitions of Project Leyden, a transformative initiative to enhance Java application performance. Focused on slashing startup time, accelerating warmup, and reducing memory footprint, Per’s talk explores how Java can evolve to meet modern demands while preserving its dynamic nature. By strategically shifting computations to optimize execution, Project Leyden introduces innovative techniques like condensers and enhanced Class Data Sharing (CDS). This session provides a roadmap for developers seeking to harness Java’s potential in high-performance environments, balancing flexibility with efficiency.

The Vision of Project Leyden

Per begins by outlining the core objectives of Project Leyden: improving startup time, warmup time, and memory footprint. Startup time, the duration from launching an application to its first meaningful output (e.g., a “Hello World” or serving a web request), is critical for user experience. Warmup time, the period until an application reaches peak performance through JIT compilation, can hinder responsiveness in dynamic systems. Footprint, encompassing memory and storage use, impacts scalability, especially in cloud environments. Per emphasizes that the best approach is to eliminate unnecessary computations, but when that’s not feasible, shifting them temporally—either earlier to compile time or later to runtime—can yield significant gains. This philosophy underpins Leyden’s strategy to refine Java’s execution model.

Shifting Computations for Efficiency

A cornerstone of Project Leyden is the concept of temporal computation shifting. Per explains that Java’s dynamic nature—encompassing dynamic class loading, JIT compilation, and runtime optimizations—enables expressive programming but can inflate startup and warmup times. By moving computations to build time, such as through constant folding or ahead-of-time (AOT) compilation, Leyden reduces runtime overhead. Alternatively, lazy evaluation postpones non-critical tasks, streamlining startup. Per introduces condensers, a novel mechanism that transforms program representations by shifting computations earlier, adding metadata, or imposing constraints on dynamism. Condensers are composable, meaning-preserving, and selectable, allowing developers to tailor optimizations based on application needs. For instance, a condenser might precompile lambda expressions into bytecode at build time, slashing runtime costs.

Enhancing Class Data Sharing (CDS)

Per delves into Class Data Sharing (CDS), a long-standing Java feature that Project Leyden enhances to achieve dramatic performance boosts. CDS allows pre-initialized JDK classes to be stored in a file, bypassing costly class loading during startup. With CDS++, Leyden extends this to include application classes, compiled code, and resolved constant pool references. Per shares compelling benchmarks: a test compiling 100 small Java files achieved a 2x startup improvement, while an XML parsing workload saw an 8x boost. For the Spring Pet Clinic benchmark, Leyden’s optimizations, including early class loading and cached compiled code, yielded up to 4x faster startup. These gains stem from a training run approach, where a representative execution gathers profiling data to inform optimizations, ensuring compatibility across platforms.

Balancing Dynamism and Performance

Java’s dynamism—encompassing dynamic typing, class loading, and reflection—empowers developers but complicates optimization. Per proposes selective constraints to balance this trade-off. For example, developers can restrict dynamic class loading for specific modules, enabling aggressive optimizations without sacrificing Java’s flexibility. The stable value feature, initially part of Leyden but now a standalone JEP, allows delayed initialization of final fields while maintaining performance akin to compile-time constants. Per illustrates this with a Fibonacci computation example, where memoization using stable values drastically reduces recursive overhead. By offering a “mixer board” of concessions, Leyden empowers developers to fine-tune performance, ensuring compatibility and preserving program semantics across diverse use cases.

Links:

PostHeaderIcon Understanding Dependency Management and Resolution: A Look at Java, Python, and Node.js

Understanding Dependency Management and Resolution: A Look at Java, Python, and Node.js

Mastering how dependencies are handled can define your project’s success or failure. Let’s explore the nuances across today’s major development ecosystems.

Introduction

Every modern application relies heavily on external libraries. These libraries accelerate development, improve security, and enable integration with third-party services. However, unmanaged dependencies can lead to catastrophic issues — from version conflicts to severe security vulnerabilities. That’s why understanding dependency management and resolution is absolutely essential, particularly across different programming ecosystems.

What is Dependency Management?

Dependency management involves declaring external components your project needs, installing them properly, ensuring their correct versions, and resolving conflicts when multiple components depend on different versions of the same library. It also includes updating libraries responsibly and securely over time. In short, good dependency management prevents issues like broken builds, “dependency hell”, or serious security holes.

Java: Maven and Gradle

In the Java ecosystem, dependency management is an integrated and structured part of the build lifecycle, using tools like Maven and Gradle.

Maven and Dependency Scopes

Maven uses a declarative pom.xml file to list dependencies. A particularly important notion in Maven is the dependency scope.

Scopes control where and how dependencies are used. Examples include:

  • compile (default): Needed at both compile time and runtime.
  • provided: Needed for compile, but provided at runtime by the environment (e.g., Servlet API in a container).
  • runtime: Needed only at runtime, not at compile time.
  • test: Used exclusively for testing (JUnit, Mockito, etc.).
  • system: Provided by the system explicitly (deprecated practice).

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.2</version>
  <scope>test</scope>
</dependency>
    

This nuanced control allows Java developers to avoid bloating production artifacts with unnecessary libraries, and to fine-tune build behaviors. This is a major feature missing from simpler systems like pip or npm.

Gradle

Gradle, offering both Groovy and Kotlin DSLs, also supports scopes through configurations like implementation, runtimeOnly, testImplementation, which have similar meanings to Maven scopes but are even more flexible.


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
    

Python: pip and Poetry

Python dependency management is simpler, but also less structured compared to Java. With pip, there is no formal concept of scopes.

pip

Developers typically separate main dependencies and development dependencies manually using different files:

  • requirements.txt – Main project dependencies.
  • requirements-dev.txt – Development and test dependencies (pytest, tox, etc.).

This manual split is prone to human error and lacks the rigorous environment control that Maven or Gradle enforce.

Poetry

Poetry improves the situation by introducing a structured division:


[tool.poetry.dependencies]
requests = "^2.31"

[tool.poetry.dev-dependencies]
pytest = "^7.1"
    

Poetry brings concepts closer to Maven scopes, but they are still less fine-grained (no runtime/compile distinction, for instance).

Node.js: npm and Yarn

JavaScript dependency managers like npm and yarn allow a simple distinction between regular and development dependencies.

npm

Dependencies are declared in package.json under different sections:

  • dependencies – Needed in production.
  • devDependencies – Needed only for development (e.g., testing libraries, linters).

{
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "mocha": "^10.2.0"
  }
}
    

While convenient, npm’s dependency management lacks Maven’s level of strictness around dependency resolution, often leading to version mismatches or “node_modules bloat.”

Key Differences Between Ecosystems

When switching between Java, Python, and Node.js environments, developers must be aware of the following fundamental differences:

1. Formality of Scopes

Java’s Maven/Gradle ecosystem defines scopes formally at the dependency level. Python (pip) and JavaScript (npm) ecosystems use looser, file- or section-based categorization.

2. Handling of Transitive Dependencies

Maven and Gradle resolve and include transitive dependencies automatically with sophisticated conflict resolution strategies (e.g., nearest version wins). pip historically had weak transitive dependency handling, leading to issues unless careful pinning is done. npm introduced better nested module flattening with npm v7+ but conflicts still occur in complex trees.

3. Lockfiles

npm/yarn and Python Poetry use lockfiles (package-lock.json, yarn.lock, poetry.lock) to ensure consistent dependency installations across machines. Maven and Gradle historically did not need lockfiles because they strictly followed declared versions and scopes. However, Gradle introduced lockfile support with dependency locking in newer versions.

4. Dependency Updating Strategy

Java developers often manually manage dependency versions inside pom.xml or use dependencyManagement blocks for centralized control. pip requires updating requirements.txt or regenerating them via pip freeze. npm/yarn allows semver rules (“^”, “~”) but auto-updating can lead to subtle breakages if not careful.

Best Practices Across All Languages

  • Pin exact versions wherever possible to avoid surprise updates.
  • Use lockfiles and commit them to version control (Git).
  • Separate production and development/test dependencies explicitly.
  • Use dependency scanners (e.g., OWASP Dependency-Check, Snyk, npm audit) regularly to detect vulnerabilities.
  • Prefer stable, maintained libraries with good community support and recent commits.

Conclusion

Dependency management, while often overlooked early in projects, becomes critical as applications scale. Maven and Gradle offer the most fine-grained controls via dependency scopes and conflict resolution. Python and JavaScript ecosystems are evolving rapidly, but require developers to be much more careful manually. Understanding these differences, and applying best practices accordingly, will ensure smoother builds, faster delivery, and safer production systems.

Interested in deeper dives into dependency vulnerability scanning, SBOM generation, or automatic dependency update pipelines? Subscribe to our blog for more in-depth content!

PostHeaderIcon [DevoxxBE2023] How Sand and Java Create the World’s Most Powerful Chips

Johan Janssen, an architect at ASML, captivated the DevoxxBE2023 audience with a deep dive into the intricate process of chip manufacturing and the role of Java in optimizing it. Johan, a seasoned speaker and JavaOne Rock Star, explained how ASML’s advanced lithography machines, powered by Java-based software, enable the creation of cutting-edge computer chips used in devices worldwide.

From Sand to Silicon Wafers

Johan began by demystifying chip production, starting with silica sand, an abundant resource transformed into silicon ingots and sliced into wafers. These wafers, approximately 30 cm in diameter, serve as the foundation for chips, hosting up to 600 chips per wafer or thousands for smaller sensors. He passed around a wafer adorned with Java’s mascot, Duke, illustrating the physical substrate of modern electronics.

The process involves printing multiple layers—up to 200—onto wafers using extreme ultraviolet (EUV) lithography machines. These machines, requiring four Boeing 747s for transport, achieve precision at the nanometer scale, with transistors as small as three nanometers. Johan likened this to driving a car 300 km and retracing the path with only 2 mm deviation, highlighting the extraordinary accuracy required.

The Role of EUV Lithography

Johan detailed the EUV lithography process, where tin droplets are hit by a 40-kilowatt laser to generate plasma at sun-like temperatures, producing EUV light. This light, directed by ultra-flat mirrors, patterns wafers through reticles costing €250,000 each. The process demands cleanroom environments, as even a single dust particle can ruin a chip, and involves continuous calibration to maintain precision across thousands of parameters.

ASML’s machines, some over 30 years old, remain in use for producing sensors and less advanced chips, demonstrating their longevity. Johan also previewed future advancements, such as high numerical aperture (NA) machines, which will enable even smaller transistors, further enhancing chip performance and energy efficiency.

Java-Powered Analytics Platform

At the heart of Johan’s talk was ASML’s Java-based analytics platform, which processes 31 terabytes of data weekly to optimize chip production. Built on Apache Spark, the platform distributes computations across worker nodes, supporting plugins for data ingestion, UI customization, and processing. These plugins allow departments to integrate diverse data types, from images to raw measurements, and support languages like Julia and C alongside Java.

The platform, running on-premise to protect sensitive data, consolidates previously disparate applications, improving efficiency and user experience. Johan highlighted a machine learning use case where the platform increased defect detection from 70% to 92% without slowing production, showcasing Java’s role in handling complex computations.

Challenges and Solutions in Chip Manufacturing

Johan discussed challenges like layer misalignment, which can cause short circuits or defective chips. The platform addresses these by analyzing wafer plots to identify correctable errors, such as adjusting subsequent layers to compensate for misalignments. Non-correctable errors may result in downgrading chips (e.g., from 16 GB to 8 GB RAM), ensuring minimal waste.

He emphasized a pragmatic approach to tool selection, starting with REST endpoints and gradually adopting Kafka for streaming data as needs evolved. Johan also noted ASML’s collaboration with tool maintainers to enhance compatibility, such as improving Spark’s progress tracking for customer feedback.

Future of Chip Manufacturing

Looking ahead, Johan highlighted the industry’s push to diversify chip production beyond Taiwan, driven by geopolitical and economic factors. However, building new factories, or “fabs,” costing $10–20 billion, faces challenges like equipment backlogs and the need for highly skilled operators. ASML’s customer support teams, working alongside clients like Intel, underscore the specialized knowledge required.

Johan concluded by stressing the importance of a forward-looking mindset, with ASML’s roadmap prioritizing innovation over rigid methodologies. This approach, combined with Java’s robustness, ensures the platform’s scalability and adaptability in a rapidly evolving industry.

Links:

PostHeaderIcon [DevoxxBE2023] Moving Java Forward Together: Community Power

Sharat Chander, Oracle’s Senior Director of Java Developer Engagement, delivered a compelling session at DevoxxBE2023, emphasizing the Java community’s pivotal role in driving the language’s evolution. With over 25 years in the IT industry, Sharat’s passion for Java and community engagement shone through as he outlined how developers can contribute to Java’s future, ensuring its relevance for decades to come.

The Legacy and Longevity of Java

Sharat began by reflecting on Java’s 28-year journey, a testament to its enduring impact on software development. He engaged the audience with a poll, revealing the diverse experience levels among attendees, from those using Java for five years to veterans with over 25 years of expertise. This diversity underscores Java’s broad adoption across industries, from small startups to large enterprises.

Java’s success, Sharat argued, stems from its thoughtful innovation strategy. Unlike the “move fast and break things” mantra, the Java team prioritizes stability and backward compatibility, ensuring that applications built on older versions remain functional. Projects like Amber, Panama, and the recent introduction of virtual threads in Java 21 exemplify this incremental yet impactful approach to innovation.

Balancing Stability and Progress

Sharat addressed the tension between rapid innovation and maintaining stability, a challenge given Java’s extensive history. He highlighted the six-month release cadence introduced to reduce latency to innovation, allowing developers to adopt new features without waiting for major releases. This approach, likened to a train arriving every three minutes, minimizes disruption and enhances accessibility.

The Java team’s commitment to trust, innovation, and predictability guides its development process. Sharat emphasized that Java’s design principles—established 28 years ago—continue to shape its evolution, ensuring it meets the needs of diverse applications, from AI and big data to emerging fields like quantum computing.

Community as the Heart of Java

The core of Sharat’s message was the community’s role in Java’s vitality. He debunked the “build it and they will come” myth, stressing that Java’s success relies on active community participation. Programs like the OpenJDK project invite developers to engage with mailing lists, review code check-ins, and contribute to technical decisions, fostering transparency and collaboration.

Sharat also highlighted foundational programs like the Java Community Process (JCP) and Java Champions, who advocate for Java independently, providing critical feedback to the Java team. He encouraged attendees to join Java User Groups (JUGs), noting the nearly 400 groups worldwide as vital hubs for knowledge sharing and networking.

Digital Engagement and Future Initiatives

Recognizing the digital era’s impact, Sharat discussed Oracle’s efforts to reach Java’s 10 million developers through platforms like dev.java. This portal aggregates learning resources, community content, and programs like JEEP Cafe and Sip of Java, which offer digestible insights into Java’s features. The recently launched Java Playground provides a browser-based environment for experimenting with code snippets, accelerating feature adoption.

Sharat also announced the community contributions initiative on dev.java, featuring content from Java Champions like Venkat Subramaniam and Hannes Kutz. This platform aims to showcase community expertise, encouraging developers to submit their best practices via GitHub pull requests.

Nurturing Diversity and Inclusion

A poignant moment in Sharat’s talk was his call for greater gender diversity in the Java community. He acknowledged the industry’s shortcomings in achieving balanced representation and urged collective action to expand the community’s mindshare. Programs like JDuchess aim to create inclusive spaces, ensuring Java’s evolution benefits from diverse perspectives.

Links:

PostHeaderIcon [DevoxxBE2023] Making Your @Beans Intelligent: Spring AI Innovations

At DevoxxBE2023, Dr. Mark Pollack delivered an insightful presentation on integrating artificial intelligence into Java applications using Spring AI, a project inspired by advancements in AI frameworks like LangChain and LlamaIndex. Mark, a seasoned Spring developer since 2003 and leader of the Spring Data project, explored how Java developers can harness pre-trained AI models to create intelligent applications that address real-world challenges. His talk introduced the audience to Spring AI’s capabilities, from simple “Hello World” examples to sophisticated use cases like question-and-answer systems over custom documents.

The Genesis of Spring AI

Mark began by sharing his journey into AI, sparked by the transformative impact of ChatGPT. Unlike traditional AI development, which often required extensive data cleaning and model training, pre-trained models like those from OpenAI offer accessible APIs and vast knowledge bases, enabling developers to focus on application engineering rather than data science. Mark highlighted how Spring AI emerged from his exploration of code generation, leveraging the structured nature of code within these models to create a framework tailored for Java developers. This framework abstracts the complexity of AI model interactions, making it easier to integrate AI into Spring-based applications.

Spring AI draws inspiration from Python’s AI ecosystem but adapts these concepts to Java’s idioms, emphasizing component abstractions and pluggability. Mark emphasized that this is not a direct port but a reimagination, aligning with the Spring ecosystem’s strengths in enterprise integration and batch processing. This approach positions Spring AI as a bridge between Java’s robust software engineering practices and the dynamic world of AI.

Core Components of AI Applications

A significant portion of Mark’s presentation focused on the architecture of AI applications, which extends beyond merely calling a model. He introduced a conceptual framework involving contextual data, AI frameworks, and models. Contextual data, akin to ETL (Extract, Transform, Load) processes, involves parsing and transforming data—such as PDFs—into embeddings stored in vector databases. These embeddings enable efficient similarity searches, crucial for use cases like question-and-answer systems.

Mark demonstrated a simple AI client in Spring AI, which abstracts interactions with various AI models, including OpenAI, Hugging Face, Amazon Bedrock, and Google Vertex. This portability allows developers to switch models without significant code changes. He also showcased the Spring CLI, a tool inspired by JavaScript’s Create React App, which simplifies project setup by generating starter code from existing repositories.

Prompt Engineering and Its Importance

Prompt engineering emerged as a critical theme in Mark’s talk. He explained that crafting effective prompts is essential for directing AI models to produce desired outputs, such as JSON-formatted responses or specific styles of answers. Spring AI’s PromptTemplate class facilitates this by allowing developers to create reusable, stateful templates with placeholders for dynamic content. Mark illustrated this with a demo where a prompt template generated a joke about a raccoon, highlighting the importance of roles (system and user) in defining the context and tone of AI responses.

He also touched on the concept of “dogfooding,” where AI models are used to refine prompts, creating a feedback loop that enhances their effectiveness. This iterative process, combined with evaluation techniques, ensures that applications deliver accurate and relevant responses, addressing challenges like model hallucinations—where AI generates plausible but incorrect information.

Retrieval Augmented Generation (RAG)

Mark introduced Retrieval Augmented Generation (RAG), a technique to overcome the limitations of AI models’ context windows, which restrict the amount of data they can process. RAG involves pre-processing data into smaller fragments, converting them into embeddings, and storing them in vector databases for similarity searches. This approach allows developers to provide only relevant data to the model, improving efficiency and accuracy.

In a demo, Mark showcased RAG with a bicycle shop dataset, where a question about city-commuting bikes retrieved relevant product descriptions from a vector store. This process mirrors traditional search engines but leverages AI to synthesize answers, demonstrating how Spring AI integrates with vector databases like Milvus and PostgreSQL to handle complex queries.

Real-World Applications and Future Directions

Mark highlighted practical applications of Spring AI, such as enabling question-and-answer systems for financial documents, medical records, or government programs like Medicaid. These use cases illustrate AI’s potential to make complex information more accessible, particularly for non-technical users. He also discussed the importance of evaluation in AI development, advocating for automated scoring mechanisms to assess response quality beyond simple test passing.

Looking forward, Mark outlined Spring AI’s roadmap, emphasizing robust core abstractions and support for a growing number of models and vector databases. He encouraged developers to explore the project’s GitHub repository and participate in its evolution, underscoring the rapid pace of AI advancements and the need for community involvement.

Links: