Blog
navigate_next
Java
Everything you need to know about Java 22
Gaurav Sharma
May 13, 2024

After announcing a 6-month release cycle for JDK updates from JDK 11, Java is changing more rapidly than ever. The latest version, Java 22, was released on March 19, 2024. In this article ,we are going to discuss the updates in Java 22.

This year,12 major JDK Enhancement Proposals (JEPs) has included in Java 22:

JEP is a system created by Oracle Corporation for gathering suggestions on how to improve the Java Development Kit and OpenJDK.

These JEPs are divided into three categories based on features:

  1. Standard:
    • Standard JEPs are fully developed features that are now part of the JDK. These have been thoroughly tested and are ready for use in regular operations. They're reliable and stable, making them great for everyday development.
  2. Preview:
    • Preview JEPs introduce new features that are complete but not yet finalized or fully supported in production. They're included in the JDK so developers can try them out and give their feedback. This trial phase helps decide if these features should be adjusted, made permanent, or removed in future updates.This feature is enabled using —enable-preview flag at compile time or run time.
  3. Incubator:
    • Incubator JEPs are experimental features that are still being tested and refined. They are included for developers to explore and provide feedback on. These are marked with incubating keyword. This helps shape the final design of the features, which might change significantly or even be dropped based on what developers think.

In Java 22, of the 12 JEPs, 4 are standard features, 7 are in preview, and 1 is an incubator feature.

Understanding some key terms:

  1. Second Preview:
    • When a JEP is labeled as "second preview," it means that the feature is being released for the second time in a preview mode. Preview features are fully implemented and specified, but are put into the JDK release for developers to use and provide feedback on. A feature might go through multiple preview phases based on the feedback received and complexity of the feature.
  2. Seventh Incubator:
    • The term "seventh incubator" indicates that a JEP is in its seventh phase of being an incubator module. Incubator modules are potentially less mature than preview features and are included in a JDK release to help developers test them and provide feedback. The "seventh" part means that the feature has been included as an incubator in seven separate releases, showing its evolution and ongoing adjustment based on user feedback and developer needs.

In this article, we will look closely at each of the updates and what problem it solves.

1. Unnamed Variables and Patterns(JEP 456)

In Java programming, there are instances where we need to declare or use variables that don’t actually need a name or specific identification because they are only used temporarily and their individual identity isn't important. This is commonly seen in situations like exception handling and lambda expressions. rephrase in more easy and understandable way.

💡 instead of those unnamed variable, underscore ( _ ) is used.

In Case of Exception Handling

In exception handling, we often catch exceptions that we need to handle, but we might not need to do anything with the exception object itself except maybe logging it or simply catching it to prevent the program from crashing. In such cases, although a variable (the exception object) is declared, it might not be explicitly named or used.

Understanding through examples:

Handling Exceptions Using Named Variable(exception)


package unnamedVariables;

public class Example1 {
    public static void main(String[] args) {
        try {
            int number = Integer.parseInt("231b");
        } catch (NumberFormatException exception) {
            System.out.println("Not a number");
        }
    }
}

Handling Exceptions Using Unnamed Variable(_)


package unnamedVariables;

public class Example2 {
    public static void main(String[] args) {
        try {
            int number = Integer.parseInt("231b");
        } catch (NumberFormatException _) {
            System.out.println("Not a number");
        }
    }
}

Output: Not a number

In Case Of Lambda Expression

In the context of lambda expressions, sometimes parameters are included in the signature because of interface requirements but are not actually used in the body of the lambda.

Iterating Over a List Using Named Variable(integer)


package unnamedVariables;

import java.util.Arrays;
import java.util.List;

public class Example3 {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(2, 4, 6, 9);
        list.forEach(integer -> System.out.println("This is an integer"));
    }
}

Iterating Over a List Using Unnamed Variable(_)

	
package unnamedVariables;

import java.util.Arrays;
import java.util.List;

public class Example4 {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(2, 4, 6, 9);
        list.forEach(_ -> System.out.println("This is an integer"));
    }
}

Output:
This is an integer
This is an integer
This is an integer
This is an integer

2. String Templates(JEP 459)

String Templates now in its 2nd preview phase in Java 22 provide a more robust and flexible way to handle string interpolation, which has been a much-requested feature from the developer community. Anyone having the basic knowledge of Javascript must know about Template Strings and how cool this feature is. It is almost similar to that .

Prior to this, Java developers had to use either string concatenation or <span class="pink">String.format()</span> for dynamic string creation, which could be cumbersome and error-prone. There is always a chance when building a complex string  using concatenation with <span class="pink">+</span> operator ,that you will miss some spaces or some formatting issues arises. to tackle this string templates have been introduced.

Note: We're not getting into detail about String Templates because in the official Java 22 presentation, it  has been mentioned clearly that this feature is going to be changed completely in the next Java update. So, it’s best not to spend too much time on it right now.

But for just a basic understanding of how string template works, a code is given below:

Classic way using String Concatenation:

	
package StringTemplate;

public class Main {
    public static void main(String[] args) {

        String user = "Gaurav";
        int points = 98;
        String message = "Hello, " + user + "! You have " + points + " points.";
        System.out.println(message);

    }
}

Classic way using <span class="pink">String.format()</span>:

	
package StringTemplate;

public class Main {
    public static void main(String[] args) {
        String user = "Gaurav";
        int points = 98;
        String message = String.format("Hello, %s! You have %d points.", user, points);
        System.out.println(message);
        
    }
}

New way using String Templates:

	
package StringTemplate;

public class Main {
    public static void main(String[] args) {
        String user = "Gaurav";
        int points = 98;
        String message = STR."Hello, \{user}! You have \{points} points.";
        System.out.println(message);

    }
}

Explanation of the above code:

The <span class="pink">STR</span> keyword and the <span class="pink">\{}</span> syntax are part of Java's new String Templates feature. This allows you to easily include variables like <span class="pink">user</span> and <span class="pink">points</span> directly in the string. The <span class="pink">STR.</span> indicates the start of a string template, and the <span class="pink">\{variableName}</span> placeholders let you insert variable values right into the text. This makes the code cleaner and easier to read, especially for strings that contain several dynamic parts.

Output:

Hello, Gaurav! You have 98 points.

💡 Good Part: If you don’t like the syntax of String Template, you can write one for your own. But as we have discussed earlier,There is a high probability that String Template will be undergoing a major revamp, so we will not discuss it more further.

3. Statements before <span class="pink">super(...)</span> (JEP 447)

In Java 22, it is now possible to execute statements before calling <span class="pink">super()</span> in a constructor.

Briefing:

In earlier versions of Java, you were not allowed to execute any statements before calling <span class="pink">super()</span> in a constructor of a derived class. This restriction was particularly restrictive.

Suppose you want to validate arguments being passed up to the superclass constructor. The common solution was to create static methods that would check the values before they were passed to <span class="pink">super()</span>. This method worked but often led to the addition of a static method or block just to check something.

With the introduction of Java 22, this constraint has been relaxed. The new feature, "Statements before super()", allows developers to include validation or other logic directly in the constructor of a derived class, right before calling <span class="pink">super()</span>. This makes the code cleaner and more straightforward, eliminating the need for separate static methods just for argument validation or any other tasks which is required before calling <span class="pink">super()</span>.

Code with no statement before super():

Main.java

	
package statementBeforeSuper;

 class Vehicle {
    private int wheels;
    private String type;

    public Vehicle(int wheels, String type) {
        this.wheels = wheels;
        this.type = type;
    }

    public void displayInfo() {
        System.out.println("Vehicle type: " + type + " with " + wheels + " wheels.");
    }
}

class Car extends Vehicle {
    public Car(int wheels, String type) {
        super(validateWheels(wheels), type);
    }

    private static int validateWheels(int numWheels) {
        if (numWheels != 4) {
            throw new IllegalArgumentException("Cars must have 4 wheels");
        }
        return numWheels;
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Car myCar = new Car(4, "SUV");
            myCar.displayInfo();
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

Explanation for the above code:

This Java code defines a <span class="pink">Car</span> class that extends a <span class="pink">Vehicle</span> class, ensuring every car has exactly 4 wheels by checking in a method called <span class="pink">validateWheels</span>. If a car is created with any number of wheels other than 4, the program will throw an error. The main method creates a car, checks the number of wheels, and displays the vehicle type and wheel count, or prints an error message if the wheel count is incorrect.

Code with statement written before super():

Main.java



package statementBeforeSuper;

class Vehicle {
    private int wheels;
    private String type;

    public Vehicle(int wheels, String type) {
        this.wheels = wheels;
        this.type = type;
    }

    public void displayInfo() {
        System.out.println("Vehicle type: " + type + " with " + wheels + " wheels.");
    }
}

class Car extends Vehicle {
    public Car(int wheels, String type) {
        int validatedWheels = validateWheels(wheels); // This statement is allowed before super in Java 22
        super(validatedWheels, type);
    }

    private static   int validateWheels(int numWheels) {
        if (numWheels != 4) {
            throw new IllegalArgumentException("Cars must have 4 wheels");
        }
        return numWheels;
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Car myCar = new Car(4, "SUV");
            myCar.displayInfo();
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

Output:

	
Vehicle type: SUV with 4 wheels.

Explanation for the above code:

In this Java code, the <span class="pink">Car</span> class constructor demonstrates the Java 22 preview feature that allows executing certain statements before calling the superclass constructor <span class="pink">super()</span>. Specifically, it validates the number of wheels for a car using a static method <span class="pink">validateWheels</span> before passing the validated number to <span class="pink">super()</span>, ensuring that all cars created by this class have exactly four wheels.

💡 Note: You cannot execute any statements involving instance variables or methods from the derived class before calling <span class="pink">super()</span>. If you do, your code will not compile because it attempts to access properties of the derived class before the superclass has been properly initialized.

→ An example of code won’t compile if you execute this code because i have removed <span class="pink">static</span> keyword from <span class="pink">validateWheels</span> method.

Main.java

	

package statementBeforeSuper;

class Vehicle {
    private int wheels;
    private String type;

    public Vehicle(int wheels, String type) {
        this.wheels = wheels;
        this.type = type;
    }

    public void displayInfo() {
        System.out.println("Vehicle type: " + type + " with " + wheels + " wheels.");
    }
}

class Car extends Vehicle {
    public Car(int wheels, String type) {
        int validatedWheels = validateWheels(wheels); // This statement is allowed before super in Java 22
        super(validatedWheels, type);
    }

    private  int validateWheels(int numWheels) {
        if (numWheels != 4) {
            throw new IllegalArgumentException("Cars must have 4 wheels");
        }
        return numWheels;
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Car myCar = new Car(4, "SUV");
            myCar.displayInfo();
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

4. Launch Multi-File Source Code Programs(JEP 458)

Java 22 introduces the ability to launch applications that consist of multiple source code files directly, without explicit compilation.

  • executes Java programs consisting of multiple java files without compiling them first.
  • As we know, from Java 11 we can execute single Java file directly without executing it but from Java 22 ,we can execute multiple files with one command without compiling it separately as we previously used to do.
  • to make it work all the classes should be present in the same folder.

Problem it solves: This feature simplifies the execution and distribution of small to medium scale Java programs, making Java more script-friendly and improving its usability in educational and rapid prototyping scenarios.

Usage example:

Greet.java

	
package multiFile;

public class Greet {

    public static String greeting() {
        return "Hello Champ!";
    }
}

Hello.java

	
package multiFile;

public class Hello {
    public static void main(String[] args) {

        System.out.println(Greet.greeting());

    }
}

In Java versions prior to JDK 22, attempting to directly compile and execute a Java file that depends on other files, such as classes defined in separate files within a directory, often resulted in a compilation error if not handled correctly. For example, if you had two classes within a folder named <span class="pink">multifile</span>—one class defined in a file called <span class="pink">Greet</span> with a static method, and another class in a file called <span class="pink">Hello</span> that calls this static method from <span class="pink">Greet</span>—direct execution of the <span class="pink">Hello</span> file without compiling <span class="pink">Greet</span> first would lead to errors.

However, starting with Java 22, improvements have been made that allow you to compile and execute multiple interdependent files together using a single command. This enhancement simplifies the process, enabling direct execution without manually managing the compilation order of the dependent files.

With Java 21:

With Java 22:

5. Implicit Classes and Instance Main Methods(JEP 463)

  • Feature Introduction: Java 22 introduces a feature known as "Implicit Classes and Instance Main Methods" (JEP 463).
  • Preview Status: This feature is currently in its second preview phase, indicating it's available for testing and feedback but not yet finalized for standard use.
  • Simplification of Java Programming: The feature significantly simplifies the Java programming experience.
  • No Explicit Class Definition Required: Developers can write and execute Java code without needing to explicitly define a public class.
  • Streamlined Main Method: The traditional main method structure, which typically requires <span class="pink">String[] args</span> as an argument, is no longer necessary.
  • Direct Code Execution: Programmers can start writing executable code directly, enhancing ease of use.
  • Reduction in Boilerplate: This approach reduces the amount of boilerplate code, making the codebase cleaner and more straightforward.
  • Benefits for Newcomers: The simplified coding process is less confusing for newcomers, making it easier to learn and adopt Java programming.

Understanding through an example:

Main.java

	
public static void main() {
    System.out.println("Hey "+inform());
}


static String inform() {
    return "i am running this program with Implicit Class Main Method";
}

Explanation for the above code:

We haven’t defined a class, and we haven’t even passed the <span class="pink">String[] args</span> as an  argument in the main method, but still, this program runs.

Output using PowerShell:

Explanation:

When we first tried running the <span class="pink">Main.java</span> file, we ran into an issue because the feature we were using, called "Implicitly Declared Classes," is still in its preview phase and isn't turned on by default. The error message was clear, instructing us to enable preview features to continue.

We re-ran the command but this time included the <span class="pink">--enable-preview</span> option and also specified <span class="pink">--source 22</span> to ensure the source compatibility with Java 22. With these adjustments, everything worked perfectly, and we got the output:

Output:


Hey i am running this program with Implicit Class Main Method

This was just to demonstrate how you can utilize Java's preview features, which aren't part of the standard language toolkit yet but can be used for testing and exploring if you explicitly enable them in the Java runtime environment. It's a great way to explore the latest updates without waiting for them to become part of the official release.

6. Stream Gatherers(JEP 461)

An extensible API for intermediate java.util.stream operations.

Similar to how the Collectors API provides a generic way to define terminal operations, the Gatherer API offers a generic approach for creating intermediate operations.

Note: The design of the <span class="pink">Gatherer</span> interface is heavily influenced by the design of <span class="pink">Collector</span>.

Stream Pipeline:

A stream pipeline in Java is a sequence of steps that processes a collection of data. It's similar to a factory assembly line where each step modifies or filters the data until it reaches its final form. The pipeline starts with a source (like a list), passes through a series of intermediate operations (like sorting or filtering), and ends with a terminal operation that either produces a single result or triggers some actions.

Understanding through a basic example:

Imagine you have a list of numbers and you want to find the sum of all even numbers greater than 10. Here’s how you would set up a stream pipeline in Java to achieve this:

	
import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(12, 19, 8, 13, 22, 15);

        int sum = numbers.stream()               // Create a stream from the list
                          .filter(n -> n > 10)   // Intermediate operation: keep only numbers greater than 10
                          .filter(n -> n % 2 == 0) // Intermediate operation: keep only even numbers
                          .reduce(0, Integer::sum); // Terminal operation: sum them up

        System.out.println("Sum of even numbers greater than 10: " + sum);
    }
}

I've talked about the stream pipeline because Stream Gatherers are used to perform intermediary operations within the pipeline.

What is Stream::gather(Gatherer)?

Stream::gather(Gatherer) is a versatile intermediate operation in Java's Stream API that allows developers to process stream elements in highly customizable ways. It is similar to Stream::collect(Collector), which is used for terminal operations, but gather is used for intermediate operations, allowing for continued stream processing.

Purpose and Functionality:Stream::gather enables the transformation and processing of stream elements through a custom mechanism defined by the developer. This operation can handle complex transformations including one-to-one, one-to-many, many-to-one, and many-to-many relationships between input and output elements. It supports operations that might require state tracking, conditional processing, or transformations that adapt over time based on previously seen elements.

Gatherer Interface:

	
Interface Gatherer<T,A,R>

Interface Gatherer takes three type parameters :

  1. T→ the input type(the type of element that it consumes)
  2. A→state type of the gatherer operation (often hidden as an implementation detail)
  3. R→ what type of element this gatherer produces

Methods in this Gatherer interface:

  1. Initializer Function (optional): This function sets up a private "state" object that remembers information needed for processing elements in the stream. For example, it can keep track of the last element seen to help decide what to do with the next one.
  2. Integrator Function: This is the main function where each element of the stream is processed. It uses the state object and can add new elements to the output or even stop the stream early if a specific condition is met, like finding the highest number.
  3. Combiner Function (optional): When the stream is split to run in parallel, this function helps to merge the separate pieces back together accurately. It ensures that the results from parallel processing are combined correctly.
  4. Finisher Function (optional): After all the elements have been processed, this function can wrap things up. It might use the state object to add more elements to the stream based on what it has learned from processing all the previous elements.

Built-in gatherers

built-in gatherers in the <span class="pink">java.util.stream.Gatherers</span> class:

  • fold: This gatherer combines all the input elements step-by-step into a single result. It only shows this final result after all the inputs have been looked at. It's good for when you need to sum up or merge everything into one outcome.
  • mapConcurrent: This gatherer processes each input element individually by applying a given function, and it can handle several elements at once, up to a certain limit. This allows for fast, parallel processing of elements, making it efficient when you have a lot of data to process quickly.
  • scan: This one takes each element and the current state (like a running total), applies a function, and sends the result out right away. It updates continuously with each new element, useful for tasks where you want to see the progression over time.
  • windowFixed: This gatherer groups elements into fixed-size lists and sends out each list once it's full. It's helpful when you need to process or analyze data in consistent blocks, like batching tasks together.
  • windowSliding: Similar to windowFixed, but after the first group, each new group shifts by one element from the previous group. This method creates overlapping groups, which is useful for analyzing data that flows continuously, like monitoring trends or changes over a moving range.

Understanding through example:

Problem Statement:

We have a List of String i.e. list of Java-related keywords. We have to organize into smaller groups. Each group should have three keywords, and we want to create only the first two groups.

With Stream Gatherers:


package streamGatherers;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Gatherers;
import java.util.stream.Stream;

public class Main {
    public static List<List<String>> findGroupsOfThreeWithGatherer(List<String> input, int fixed_size, int grouping) {
        return Stream.iterate(0, i -> i + 1)
                .map(index -> input.get(index))
                .limit(input.size())
                .gather(Gatherers.windowFixed(fixed_size))
                .limit(grouping)
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        List<String> javaKeyWords = List.of(
                "Java",
                "Jakarta EE",
                "SpringBoot",
                "Micronaut",
                "Quarkus",
                "VirtualThreads",
                "GarbageCollector"
        );
        List<List<String>> grouped = findGroupsOfThreeWithGatherer(javaKeyWords, 3, 2);
        System.out.println(grouped);
    }
}

Output:

	
[[Java, Jakarta EE, SpringBoot], [Micronaut, Quarkus, VirtualThreads]]

Each feature has a lot to offer and we can’t cover everything in great detail. Dive in if you find any of them intriguing.

7. Regional Pinning for G1(JEP 423)

Regional Pinning has now reached its final phase, making it production-ready and a standard feature in Java 22.

About G1→Java Default Garbage Collector:

💡 The G1 (Garbage-First) is a type of garbage collector used in Java, designed to manage and free up memory in a computer efficiently. It is called "Garbage-First" because it prioritizes and quickly cleans areas with the most garbage, or unused data, first. This helps improve the performance of Java applications by ensuring they have the memory resources they need without delay. G1 is often set as the default garbage collector because it works well across different types of applications, balancing memory cleanup with running time.

Understanding with an Analogy:

💡 Imagine your computer's memory as a desk with lots of papers (data) scattered around. Over time, some papers are no longer needed and just take up space. G1 works like an efficient office worker who quickly sorts through the papers, recycling the ones you don’t need anymore to make room for new ones. This helps keep your computer running smoothly by efficiently organizing memory without slowing down your work.

Briefing:

There are many libraries written in C, C++, and other languages that are extremely useful, yet they cannot be accessed directly from Java applications. To bridge this gap, we rely on JNI (Java Native Interface), a crucial component that allows Java applications to both call and be called by native applications and libraries. JNI provides functions such as <span class="pink">GetPrimitiveArrayCritical</span> to obtain and <span class="pink">ReleasePrimitiveArrayCritical</span> to release direct pointers to Java objects.

These functions must be carefully used in pairs: first obtaining a pointer to an object, and then, after using the object, releasing the pointer.The code executed between obtaining and releasing these pointers is considered a critical region, during which the JVM must ensure that the GC does not move the associated Java object.

In these critical regions, the JVM is required to ensure that the garbage collector (GC) does not move the associated Java objects, maintaining the integrity and stability required for native operations.

Problem History:

Historically, the G1 garbage collector, the default choice in Java, would entirely disable garbage collection during these critical periods. While this method ensures the stability of Java objects during native calls, it comes with several drawbacks. The stopping of GC activities can lead to significant latency issues, thread stalling, and in some cases, it might even push the system into out-of-memory conditions, potentially causing premature shutdowns of the JVM.

Solution:

Previously, we had to stop all garbage collection if a critical object was being used, which could slow things down. Now, we don't need to stop everything. Instead, the garbage collector simply avoids the areas with critical objects and continues cleaning up the rest of the memory. This keeps the application running more smoothly.

Addressing these challenges, JEP 423 introduces an innovative solution by implementing region pinning within the G1 garbage collector. This enhancement allows specific memory regions containing critical objects to be pinned, thus exempt from being moved during garbage collection cycles. This approach ensures that garbage collection can proceed in other parts of the heap without disrupting the integrity of the objects involved in native interactions. The result is a more efficient GC process, reducing latency, preventing thread stalling, and avoiding out-of-memory errors, thereby enhancing the reliability and performance of Java applications interacting with unmanaged languages.

How it works?

JEP 423 has changed the way the G1 garbage collector works by introducing region pinning. Instead of stopping the entire garbage collection process, it now allows specific areas of memory that hold important objects to be "pinned" or locked down during cleaning. This happens by keeping track of how many critical objects are in each area. The count goes up when an object is accessed and down when it's released. If there's even one critical object left, that area won't be touched during garbage collection. This approach keeps those important objects in place, avoiding the need to stop all garbage collection, which helps reduce delays significantly.

💡 Java 22 offers a cool new alternative for working with libraries from other languages, reducing our dependency on JNI and its associated overhead. This alternative is JEP 454, the Foreign Function and Memory API, which we'll dive into later.

8. Foreign Function and Memory API(JEP 454)

Project Panama:

Project Panama is an initiative by the Java community to improve the connection between the Java Virtual Machine (JVM) and native code, which is often written in languages like C and C++. Foregin Function and Memory API comes under the project Panama to make the interaction more suiutable between Java and Non-Java API.

Briefing:

JNI(Java Native Interface) has long been the sole method for accessing native libraries from Java, but its complex and fragile programming model makes it cumbersome for large-scale use. However, this is no longer the case: JDK 22 introduces the Foreign Function & Memory API, offering a safe, contemporary, and efficient approach to interfacing with foreign memory and code directly from Java.

Native Code:

Native code refers to software or program instructions that are written to run directly on a specific processor architecture and its operating system.

It’s written to work on one particular type of computer or device. This is different from general code written in languages like Java or Python, which are more like universal languages that need a translator (like a Java Virtual Machine for Java) to help the computer understand what to do.

Understanding Native Code through Analogy:

💡 Imagine you’re traveling and you speak directly in the local language of the place you’re visiting — that's like native code. It's fast and efficient because there’s no need for translation. But if you're using a universal language and rely on a translator, it might take more time to communicate, just like how Java works on different devices.

Native code is really good for tasks where speed and efficiency are super important, such as video games or programs that handle lots of data quickly. It’s like having a direct conversation with the computer’s hardware without any middle steps.

Important:

Note: We can't explore every feature in great detail or address all the features in a single article. Therefore, we won't delve deeply into the FFM API and other features here. In our next article, we will aim to cover these topics more thoroughly.

Cheers.

Keep coding and have fun!

Gaurav Sharma
May 13, 2024
Use Unlogged to
mock instantly
record and replay methods
mock instantly
Install Plugin