Saturday, 14 June 2025

Unlocking Native Power: A Deep Dive into Java Foreign Function & Memory API (FFM API)

Seamlessly Call Native Code and Manage Off-Heap Memory with Java’s Game-Changing FFM API

Table of Contents

  1. Introduction to Java FFM API

  2. Why Java Needed the FFM API

  3. Modules and Packages

  4. Calling Native Code with Foreign Function

  5. Memory Access and Allocation

  6. Key Classes: Linker, MemorySegment, MemoryLayout, Arena, etc.

  7. Example: Calling strlen from C

  8. Benefits and Use Cases

  9. Limitations and Considerations

  10. Future of FFM in Java

  11. Conclusion

1. Introduction to Java FFM API

The Java Foreign Function & Memory API (FFM API) is a new feature introduced to replace the traditional Java Native Interface (JNI), providing a pure Java mechanism for:

  • Calling native code (like C/C++ functions)

  • Accessing native memory

  • Structuring memory layouts

As of JDK 22, the FFM API is stable under the java.lang.foreign package and aims to improve safety, performance, and developer experience compared to JNI.

2. Why Java Needed the FFM API

Before FFM, Java relied on JNI to interact with native libraries. JNI is:

  • Verbose and error-prone

  • Hard to debug

  • Not type-safe

  • Requires compiling C glue code

The FFM API solves this by providing:

  • A type-safe, pure Java way to interoperate with native libraries

  • No need for additional native code wrappers

  • Better integration with the Java memory model

3. Modules and Packages

To use FFM, ensure you're using JDK 22+ and add the required module:

bash
--enable-preview --add-modules jdk.incubator.foreign

(If using a preview version like JDK 19–21)

As of JDK 22, it’s part of the standard API:

java
import java.lang.foreign.*; import java.lang.invoke.*;

4. Calling Native Code with Foreign Function

To call a native function:

  1. Obtain a Linker object

  2. Load the native library (e.g., libc)

  3. Look up the function address

  4. Describe the method signature using FunctionDescriptor

  5. Create a MethodHandle

Example

java
Linker linker = Linker.nativeLinker(); SymbolLookup lookup = SymbolLookup.systemLookup(); MemorySegment strlenFunc = lookup.find("strlen").orElseThrow(); FunctionDescriptor strlenDesc = FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); MethodHandle strlen = linker.downcallHandle(strlenFunc, strlenDesc); String input = "Hello FFM!"; try (Arena arena = Arena.ofConfined()) { MemorySegment str = arena.allocateUtf8String(input); long result = (long) strlen.invoke(str); System.out.println("String length: " + result); }

5. Memory Access and Allocation

The FFM API lets you work with memory outside the JVM heap, useful for:

  • Shared memory

  • Performance-critical applications

  • Interfacing with C-style structures

MemorySegment

Represents a region of memory, can be:

  • Allocated manually

  • Mapped from a file

  • Passed from native code

java
try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(100); }

6. Key Classes

ClassDescription
LinkerConnects Java with native code
MemorySegmentRepresents a block of memory
ArenaManages memory lifetimes
FunctionDescriptorDescribes native function signatures
MemoryLayoutDescribes memory struct layouts
ValueLayoutPredefined layouts for primitive types
SegmentAllocatorAllocates memory from an arena

7. Example: Calling strlen from C

Let’s walk through calling the C function strlen():

Step-by-step:

  1. Use SymbolLookup.systemLookup() to find strlen

  2. Use Linker.downcallHandle to get a handle to the function

  3. Use arena.allocateUtf8String() to convert a Java string to native UTF-8

  4. Invoke the method

java
Linker linker = Linker.nativeLinker(); SymbolLookup lookup = SymbolLookup.systemLookup(); MemorySegment strlenAddr = lookup.find("strlen").orElseThrow(); MethodHandle strlen = linker.downcallHandle(strlenAddr, FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)); try (Arena arena = Arena.ofConfined()) { MemorySegment str = arena.allocateUtf8String("Foreign Function!"); long len = (long) strlen.invoke(str); System.out.println("Length: " + len); // Output: 17 }

8. Benefits and Use Cases

✅ Benefits:

  • Type-safe API

  • No native code needed

  • Better performance and memory control

  • Cross-platform

  • Cleaner than JNI

🛠️ Use Cases:

  • Performance-intensive apps (games, simulations)

  • Interfacing with C/C++ libraries (e.g., OpenCV, SQLite)

  • Hardware interaction (e.g., drivers, sensors)

  • Shared memory and IPC


9. Limitations and Considerations

  • Requires JDK 22+ (or earlier with preview flags)

  • Not all native libraries are easily interoperable

  • No automatic struct mapping like in JNA (yet)

  • Reflection is required for function calls (via MethodHandle)

  • Platform-dependent function names may vary (libc, msvcrt, etc.)


10. Future of FFM in Java

The FFM API is a key pillar in Project Panama. Upcoming improvements include:

  • Struct mapping APIs

  • Simplified linker configuration

  • Improved layout inference

  • Native callbacks from C to Java

With broader adoption and JDK tooling support (like jextract), the FFM API is poised to become a mainstream feature for Java-native interop.


11. Conclusion

The Java Foreign Function & Memory API modernizes Java’s approach to native interop. By replacing JNI with a safer, more flexible model, it empowers Java developers to:

  • Call native functions easily

  • Access native memory safely

  • Create performance-critical applications with better memory control

It’s time to explore the native world, without leaving the comfort of Java.


🔗 Further Reading and Tools

Thursday, 5 June 2025

Enhance Your Java Code with These Must-Know JDK 24 Features

Discover how JDK 24’s latest features can streamline your development, improve performance, and make your Java code more expressive than ever.

Java continues to evolve with each new release, and JDK 24 is no exception. This latest version introduces a host of powerful features that enhance performance, improve productivity, and add more expressiveness to the language. In this article, we’ll explore the top 10 game-changing features in JDK 24 that every Java developer should be aware of.

JDK 24 is Here! Game-Changing Features Every Java Developer Must Know
Image Copyright @tech693.com


1. Primitive Types in Patterns and instanceof (Preview)

With JDK 24, pattern matching support is extended to primitive types, enabling more expressive and safer type checks. This allows instanceof and switch to work directly with primitive values.

Benefits:

  • Eliminates boilerplate casting.

  • Enhances readability and type safety.

Example:

Object obj = 42;
if (obj instanceof int i) {
    System.out.println("Primitive int: " + i);
}

2. Record Patterns & Pattern Matching for switch (Final)

Two long-awaited features are now finalized:

  • Record Patterns (JEP 440)

  • Pattern Matching for switch (JEP 441)

These features allow for concise, readable, and type-safe deconstruction of data records.

Example:

record Person(String name, int age) {}

static void printPerson(Object obj) {
    switch (obj) {
        case Person(String name, int age) -> System.out.println(name + " is " + age);
        case null -> System.out.println("Null value");
        default -> System.out.println("Unknown type");
    }
}

3. Foreign Function & Memory API (Third Preview)

JEP 454 introduces an advanced and safer alternative to JNI. The Foreign Function & Memory API enables Java applications to interoperate with native libraries in a more controlled and memory-safe manner.

Use Cases:

  • High-performance computing.

  • Native library integrations.

  • Games and multimedia applications.

4. Virtual Threads (Still Final)

Although finalized in JDK 21, Virtual Threads remain a cornerstone of Java’s concurrency model in JDK 24.

Key Points:

  • Lightweight, cheap to create.

  • Designed for scalability.

  • Perfect for I/O-bound and highly concurrent applications.

Example:

Runnable task = () -> System.out.println(Thread.currentThread());
Thread.startVirtualThread(task);


5. Scoped Values (Incubator)

JEP 464 introduces Scoped Values, a more robust and safer alternative to ThreadLocal. These are designed for sharing immutable data within and across threads, especially virtual threads.

Benefits:

  • Thread-safe context propagation.

  • Cleaner and more maintainable code.

6. Class-File API (Preview)

JEP 457 delivers a new standard API for reading, writing, and analyzing .class files without relying on third-party libraries like ASM.

Ideal For:

  • Tool developers.

  • Static analyzers.

  • Custom compilers and frameworks.

7. Statements Before super(...) (Preview)

JEP 447 allows developers to write statements before super(...) in constructors. This is particularly useful for initializing fields or validating inputs before calling a superclass constructor.

Example:

class Child extends Parent {
    Child(int x) {
        validate(x);
        super(x);
    }
}

8. Unnamed Variables and Patterns (Second Preview)

JEP 456 enables the use of _ to ignore variables or parts of a pattern that are not needed.

Use Cases:

  • Cleaner switch statements.

  • Avoids unnecessary variable declarations.

Example:

record Point(int x, int y) {}
switch (point) {
    case Point(_, int y) -> System.out.println("Y is " + y);
}

9. String Templates (Second Preview)

JEP 465 brings String Templates, allowing developers to write cleaner, more readable, and format-safe string construction logic.

Example:

String name = "Alice";
int age = 30;
String msg = STR."Hello, my name is \{name} and I am \{age} years old.";

10. Performance & GC Enhancements

JDK 24 brings significant performance improvements across GCs:

  • G1: Improved pause times.

  • ZGC: Better memory reclamation.

  • Shenandoah: Reduced latency.

These changes enhance overall throughput and minimize memory-related issues in large-scale applications.

Conclusion

JDK 24 is a milestone release that delivers on both developer ergonomics and runtime efficiency. From modern language enhancements to deeper runtime integrations and performance boosts, this release is packed with features that make Java more powerful and enjoyable to work with.

Saturday, 31 May 2025

Stop Writing If-Else Pyramids: Use the State Pattern Instead

 Introduction

As software engineers, we often find ourselves writing code that responds to different conditions and executes varying logic based on the system’s current state. A common way of handling such variability is by using nested if-else or switch statements. While this works fine for simple scenarios, it can quickly become unwieldy, hard to maintain, and error-prone as complexity grows.

In this article, we’ll discuss why if-else trees are problematic in complex systems and how the State Design Pattern offers a cleaner, more scalable, and maintainable solution. We’ll go through real-world examples, refactor legacy if-else trees into the State pattern, and understand how this pattern can enhance code readability, reduce bugs, and enable more flexible software architecture.

Table of Contents

  1. The Problem with If-Else Trees

  2. Introducing the State Pattern

  3. Anatomy of the State Pattern

  4. When to Use the State Pattern

  5. Refactoring If-Else Trees with State Pattern: A Real Example

  6. Benefits of Using the State Pattern

  7. State Pattern in Functional Languages

  8. Anti-Patterns and Pitfalls

  9. Testability and Maintainability Improvements

  10. Conclusion

1. The Problem with If-Else Trees

The Classic Monster

Consider a simple vending machine:

public class VendingMachine {
    public void handleAction(String state, String action) {
        if (state.equals("IDLE")) {
            if (action.equals("insert_coin")) {
                System.out.println("Coin inserted.");
                // move to WAITING_FOR_SELECTION
            } else {
                System.out.println("Invalid action.");
            }
        } else if (state.equals("WAITING_FOR_SELECTION")) {
            if (action.equals("select_item")) {
                System.out.println("Item selected.");
                // move to DISPENSING
            } else {
                System.out.println("Invalid action.");
            }
        } else if (state.equals("DISPENSING")) {
            if (action.equals("dispense_item")) {
                System.out.println("Dispensing item...");
                // move to IDLE
            } else {
                System.out.println("Invalid action.");
            }
        }
    }
}

What’s Wrong?

  • Low Scalability: Adding a new state or action requires editing multiple conditional branches.

  • Poor Readability: Business logic gets buried under control flow noise.

  • Brittle Code: Mistakes in string literals or order of conditionals can lead to subtle bugs.

  • Code Duplication: Similar validation logic is repeated.

2. Introducing the State Pattern

The State Pattern is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object has changed its class.

Definition (Gang of Four):

"Allow an object to alter its behavior when its internal state changes. The object will appear to change its class."

This pattern encapsulates state-specific behavior into separate state classes, and the context class delegates behavior based on its current state.

3. Anatomy of the State Pattern

Participants

  • Context: Maintains an instance of a ConcreteState and delegates the work.

  • State Interface: Declares method(s) that ConcreteStates will implement.

  • Concrete States: Implement state-specific behavior.

UML Diagram

State Design Pattern


4. When to Use the State Pattern

You should consider using the State pattern when:

  • An object’s behavior depends on its state.

  • You have multiple conditional branches (if-else or switch-case) based on a state field.

  • You want to avoid long methods with complex conditional logic.

  • The behavior changes frequently or requires extension.

5. Refactoring If-Else Trees with State Pattern: A Real Example

Let’s revisit our vending machine and refactor it using the State pattern in Java.

Step 1: Define the State Interface

public interface State {
    void insertCoin(VendingMachine machine);
    void selectItem(VendingMachine machine);
    void dispenseItem(VendingMachine machine);
}

Step 2: Create Concrete States

public class IdleState implements State {
    public void insertCoin(VendingMachine machine) {
        System.out.println("Coin inserted.");
        machine.setState(new WaitingForSelectionState());
    }

    public void selectItem(VendingMachine machine) {
        System.out.println("Insert coin first.");
    }

    public void dispenseItem(VendingMachine machine) {
        System.out.println("Insert coin and select item first.");
    }
}
public class WaitingForSelectionState implements State {
    public void insertCoin(VendingMachine machine) {
        System.out.println("Coin already inserted.");
    }

    public void selectItem(VendingMachine machine) {
        System.out.println("Item selected.");
        machine.setState(new DispensingState());
    }

    public void dispenseItem(VendingMachine machine) {
        System.out.println("Select item first.");
    }
}
public class DispensingState implements State {
    public void insertCoin(VendingMachine machine) {
        System.out.println("Wait for current dispensing to complete.");
    }

    public void selectItem(VendingMachine machine) {
        System.out.println("Already dispensing.");
    }

    public void dispenseItem(VendingMachine machine) {
        System.out.println("Dispensing item...");
        machine.setState(new IdleState());
    }
}

Step 3: Create the Context Class

public class VendingMachine {
    private State state;

    public VendingMachine() {
        this.state = new IdleState(); // initial state
    }

    public void setState(State state) {
        this.state = state;
    }

    public void insertCoin() {
        state.insertCoin(this);
    }

    public void selectItem() {
        state.selectItem(this);
    }

    public void dispenseItem() {
        state.dispenseItem(this);
    }
}

Usage

public class Main {
    public static void main(String[] args) {
        VendingMachine machine = new VendingMachine();

        machine.insertCoin();     // Coin inserted.
        machine.selectItem();     // Item selected.
        machine.dispenseItem();   // Dispensing item...
    }
}

6. Benefits of Using the State Pattern

✅ Improved Readability

Each state’s behavior is encapsulated in a separate class, making the logic easy to follow.

✅ Open/Closed Principle

You can add new states without changing the context or existing state logic.

✅ Elimination of Conditionals

No more deep nesting of if-else trees based on state.

✅ Easier Testing

Each state can be tested independently without requiring the full system.


7. State Pattern in Functional Languages

While classical object-oriented languages implement the State pattern using interfaces and polymorphism, functional languages achieve similar benefits using function composition and pattern matching.

Example in Kotlin

sealed class VendingMachineState {
    object Idle : VendingMachineState()
    object Waiting : VendingMachineState()
    object Dispensing : VendingMachineState()
}

fun handle(state: VendingMachineState, action: String): VendingMachineState {
    return when (state) {
        is VendingMachineState.Idle -> {
            if (action == "insert_coin") {
                println("Coin inserted.")
                VendingMachineState.Waiting
            } else {
                println("Invalid.")
                state
            }
        }
        is VendingMachineState.Waiting -> {
            if (action == "select_item") {
                println("Item selected.")
                VendingMachineState.Dispensing
            } else {
                println("Invalid.")
                state
            }
        }
        is VendingMachineState.Dispensing -> {
            if (action == "dispense_item") {
                println("Dispensed.")
                VendingMachineState.Idle
            } else {
                println("Invalid.")
                state
            }
        }
    }
}

8. Anti-Patterns and Pitfalls

❌ Over-Engineering

Don't use the State pattern if you only have one or two states with minor differences. The added complexity may not be worth it.

❌ Tight Coupling Between States

Avoid making states aware of each other’s internals. Let the Context manage transitions.

❌ Mutable Global State

Ensure state transitions are deterministic and not affected by hidden mutable fields.


9. Testability and Maintainability Improvements

The State pattern simplifies unit testing:

  • You can create a test per state class.

  • Mocking or stubbing other parts of the system is easier since each state is small and focused.

Example: Testing IdleState

@Test public void testInsertCoinInIdleState() { VendingMachine machine = new VendingMachine(); machine.insertCoin(); assertTrue(machine.getState() instanceof WaitingForSelectionState); }

10. Conclusion

The next time you find yourself writing a complex if-else or switch-case tree based on an object's state, consider using the State pattern instead. It’s a powerful tool in your design toolbox that can make your code:

  • Cleaner

  • More extensible

  • Easier to test

  • Aligned with SOLID principles

By encapsulating behaviors in discrete state classes, you enable your codebase to grow organically while remaining robust and readable.

So stop writing if-else jungles — embrace the State pattern, and write better, more maintainable software!

Further Reading

  • Design Patterns: Elements of Reusable Object-Oriented Software by Gamma et al.

  • Martin Fowler’s Refactoring and Patterns of Enterprise Application Architecture

  • “State Machines and Statecharts” by David Harel

Wednesday, 25 December 2024

What is Ansible Lint and how it can improve quality, security and maintainability of Ansible automation scripts

Introduction to Ansible Lint

Ansible Lint is a tool designed to check Ansible playbooks, roles, and tasks against best practices and coding standards. It helps identify potential issues, inconsistencies, and deviations from recommended practices, promoting better maintainability and reliability of Ansible code.

Key Features of Ansible Lint:

  1. Syntax Checking: Ensures the syntax of tasks, playbooks, and roles is correct.
  2. Best Practice Enforcement: Detects violations of Ansible best practices, such as hardcoding variables or using insecure configurations.
  3. Custom Rules: Allows users to define their own linting rules to meet specific project requirements.
  4. Integration: Works with CI/CD pipelines to automate code checks during development.

How to Use Ansible Lint:

  1. Install Ansible Lint: You can install Ansible Lint using pip:

    pip install ansible-lint
  2. Run Ansible Lint: To check a playbook:

    ansible-lint your-playbook.yml

    To check a role directory:

    ansible-lint roles/your-role/
  3. Automate Linting:

    • Integrate it into version control workflows (e.g., Git hooks).
    • Add it as a step in CI/CD pipelines to enforce consistent standards.

Writing Better Ansible Code Using Ansible Lint:

  1. Follow Best Practices:

    • Use variables for dynamic values rather than hardcoding them.
    • Write idempotent tasks to avoid unintended changes.
    • Use descriptive names for roles, tasks, and variables.
  2. Keep Playbooks Simple and Readable:

    • Avoid deeply nested structures; break down complex playbooks into smaller roles.
    • Use comments to explain non-intuitive tasks or decisions.
  3. Use Handlers for Notifications: Ensure tasks trigger handlers where necessary, for example:

    - name: Install nginx apt: name: nginx state: present notify: Restart nginx
  4. Adhere to Security Best Practices:

    • Avoid using plain-text passwords.
    • Leverage Ansible Vault for sensitive data.
  5. Fix Linting Issues Promptly: Review and address issues flagged by Ansible Lint. For instance:

    • Warning: ANSIBLE0002: Trailing whitespace
      Fix: Remove trailing whitespace in the playbook.
  6. Write Tests for Roles: Use tools like molecule to test roles in isolated environments, ensuring that changes don’t break functionality.

  7. Customize Ansible Lint Rules:

    • Use a .ansible-lint configuration file to tailor checks to your project's needs.
    • Example .ansible-lint configuration:
      skip_list: - 'no-changed-when' - 'command-instead-of-shell'

By leveraging Ansible Lint consistently, you can improve the quality, security, and maintainability of your Ansible automation scripts.