{"id":19468809,"url":"https://github.com/imrafaelmerino/java-fun","last_synced_at":"2026-01-30T16:47:16.744Z","repository":{"id":263677143,"uuid":"848175330","full_name":"imrafaelmerino/java-fun","owner":"imrafaelmerino","description":"java-fun simplifies Property-Based Testing in Java by providing powerful and composable Pseudo Random Generators. These generators enable the creation of diverse test cases effortlessly, making testing more effective and intuitive for developers.","archived":false,"fork":false,"pushed_at":"2024-11-23T21:53:32.000Z","size":170328,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-19T08:28:31.314Z","etag":null,"topics":["functional-programming","generator","java","jmeter","property-based-testing"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/imrafaelmerino.png","metadata":{"files":{"readme":"docs/README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-08-27T09:20:29.000Z","updated_at":"2024-11-23T21:53:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"3320b36f-37dd-4901-90ab-5d16fc9abd72","html_url":"https://github.com/imrafaelmerino/java-fun","commit_stats":{"total_commits":85,"total_committers":2,"mean_commits":42.5,"dds":"0.22352941176470587","last_synced_commit":"64303c64b6a181a6dcd7a44a55e0f07261d01fa3"},"previous_names":["imrafaelmerino/java-fun"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/imrafaelmerino/java-fun","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fjava-fun","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fjava-fun/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fjava-fun/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fjava-fun/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imrafaelmerino","download_url":"https://codeload.github.com/imrafaelmerino/java-fun/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fjava-fun/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28915937,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T16:37:38.804Z","status":"ssl_error","status_checked_at":"2026-01-30T16:37:37.878Z","response_time":66,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","generator","java","jmeter","property-based-testing"],"created_at":"2024-11-10T18:44:03.524Z","updated_at":"2026-01-30T16:47:16.736Z","avatar_url":"https://github.com/imrafaelmerino.png","language":"Java","readme":"\u003cimg src=\"./logo/package_twitter_if9bsyj4/base/full/coverphoto/base_logo_white_background.png\" alt=\"logo\"/\u003e\n\n[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-%E2%98%95%20Support-yellow)](https://www.buymeacoffee.com/imrafaelmerino)\n\n[![Maven](https://img.shields.io/maven-central/v/com.github.imrafaelmerino/java-fun/3.0.0)](https://search.maven.org/artifact/com.github.imrafaelmerino/java-fun/3.0.0/jar)\n\n\"_When in doubt, use brute force._\" **Ken Thompson**\n\n-   [Goal](#goal)\n-   [Pseudo Random Generators](#prg)\n    -   [Primitive Types Generators](#ptg)\n    -   [Collection Generators](#cg)\n    -   [Tuples and Record Generators](#trg)\n    -   [Combinators](#com)\n    -   [Objects Generators](#og)\n    -   [Recursive generators](#rg)\n    -   [Useful and common patterns](#ucp)\n    -   [Using generators in JMeter Test Plans](#jmeter)\n-   [Optics](#optics)\n-   [Installation](#inst)\n-   [Related projects](#rp)\n\n## \u003ca name=\"goal\"\u003e\u003ca/\u003e Goal\n\nThe primary objective of java-fun is to bring essential Functional Programming (FP) patterns to the\nJava ecosystem. Unlike mere translations of these patterns from other languages, java-fun is\ndesigned with the intent that any typical Java developer can effortlessly embrace and comprehend\nthese concepts. The emphasis is on preserving the essence of these patterns, ensuring developers do\nnot become entangled in unfamiliar types and conventions.\n\nHere are the key concepts that have been thoughtfully implemented within **java-fun**:\n\n-   **Pseudo Random Generators**: Property-Based Testing is a highly effective testing approach, and\n    having a robust set of generators that can be composed in countless ways is crucial.\n    **java-fun** simplifies this process, making it incredibly straightforward.\n\n-   **Optics**: In functional programming, optics take precedence over traditional getters and\n    setters. They offer safety and composability, eliminating the likelihood of encountering\n    NullPointerExceptions when used correctly.\n\n-   **Tuples**: Although Java may not officially support tuples, they remain valuable. **java-fun**\n    introduces tuples with arities of two and three.In the same vein that lambdas represent nameless\n    functions, tuples can be seen as records lacking names. There are situations where assigning\n    names becomes more cumbersome than beneficial...\n\n## \u003ca name=\"prg\"\u003e\u003ca/\u003e Pseudo Random Generators\n\nPseudorandom number generators (PRNs) play a vital role in practical scenarios due to their rapid\nnumber generation capabilities and the ability to produce reproducible results. These attributes are\nparticularly valuable in testing contexts.\n\nIn **java-fun**, we represent a PRN with the `Gen` type:\n\n```code\nimport fun.gen.Gen;\n\nimport java.util.RandomGenerator;\n\npublic interface Gen\u003cO\u003e extends Function\u003cRandomGenerator, Supplier\u003cO\u003e\u003e {\n}\n```\n\nHere, a `Gen` is essentially a function that accepts a `Random` seed and yields a lazy computation\nof type `O`. The lazy nature of these computations is essential for seamless generator composition.\n\nTo create generators, you have access to two fundamental static factory methods:\n\n-   **arbitrary**: This method produces generators that offer a uniform distribution of values.\n-   **biased**: This method generates values with varying probabilities, giving higher probabilities\n    to values that are known to trigger more bugs in our code. This is a crucial aspect of\n    Property-Based Testing.\n\nWhile you can create custom generators by implementing the `Gen` interface, **java-fun** provides a\nplethora of predefined generators for your convenience. Let's delve into these predefined\ngenerators.\n\n### \u003ca name=\"ptg\"\u003e\u003ca/\u003e Primitive Types Generators\n\nIn `java-fun`, we provide a range of generators for primitive data types to facilitate\nProperty-Based Testing. These generators are crucial for efficient testing and come in various\nflavors. Let's explore them in detail:\n\n#### **String Generators**\n\n-   **Bounded String Biased Generator**: This generator is biased towards producing specific string\n    values. It generates the empty string, blank strings, and strings of lengths within the\n    specified range with higher probability, making it useful for focusing on scenarios prone to\n    bugs.\n\n    ```code\n    Gen\u003cString\u003e gen = StrGen.biased(int minLength, int maxLength);\n    ```\n\n    For instance:\n\n    ```code\n    Gen\u003cString\u003e gen = StrGen.biased(0, 3);\n    ```\n\n    This might produce strings like \"\", \" \", or any string of length from zero to three, composed of\n    valid printable Unicode characters.\n\n-   **Bounded String Arbitrary Generators**: These generators produce strings of lengths uniformly\n    distributed within the specified range `[minLength, maxLength]`. Unlike the biased generator,\n    all values within this range are generated with equal probability.\n\n    ```code\n    Gen\u003cString\u003e gen = StrGen.arbitrary(int minLength, int maxLength);\n    ```\n\n    Other types of bounded string arbitrary generators include:\n\n    ```code\n    Gen\u003cString\u003e gen = StrGen.alphanumeric(int minLength, int maxLength);\n    Gen\u003cString\u003e gen = StrGen.alphabetic(int minLength, int maxLength);\n    Gen\u003cString\u003e gen = StrGen.letters(int minLength, int maxLength);\n    Gen\u003cString\u003e gen = StrGen.digits(int minLength, int maxLength);\n    Gen\u003cString\u003e gen = StrGen.ascii(int minLength, int maxLength);\n    ```\n\n#### **Integer Generators**\n\n-   **Unbounded Integer Biased Generator**: This generator focuses on producing specific integer\n    values with higher probability. It generates zero, `Byte.MAX_VALUE`, `Byte.MIN_VALUE`,\n    `Short.MAX_VALUE`, `Short.MIN_VALUE`, `Integer.MAX_VALUE`, and `Integer.MIN_VALUE`.\n\n    ```code\n    Gen\u003cInteger\u003e gen = IntGen.biased();\n    ```\n\n-   **Bounded Integer Biased Generator**: Similar to the unbounded version, this generator produces\n    values within the specified interval `(min, max)` with a higher probability.\n\n    ```code\n    Gen\u003cInteger\u003e gen = IntGen.biased(int min, int max);\n    ```\n\n-   **Unbounded Integer Arbitrary Generator**: This generator produces any integer number with the\n    same probability, following a uniform distribution.\n\n    ```code\n    Gen\u003cInteger\u003e gen = IntGen.arbitrary();\n    ```\n\n-   **Bounded Integer Arbitrary Generator**: Similar to the unbounded version, this generator\n    produces integers between `min` and `max` (inclusive) with the same probability, following a\n    uniform distribution.\n\n    ```code\n    Gen\u003cInteger\u003e gen = IntGen.arbitrary(int min, int max);\n    ```\n\n#### **Long Generators**\n\n-   **Unbounded Long Biased Generator**: This generator focuses on producing specific long values\n    with higher probability. It generates values such as zero, `Byte.MAX_VALUE`, `Byte.MIN_VALUE`,\n    `Short.MAX_VALUE`, `Short.MIN_VALUE`, `Integer.MAX_VALUE`, `Integer.MIN_VALUE`,\n    `Long.MIN_VALUE`, and `Long.MAX_VALUE`.\n\n    ```code\n    Gen\u003cLong\u003e gen = LongGen.biased();\n    ```\n\n-   **Bounded Long-Biased Generator**: Similar to the unbounded version, this generator produces\n    values within the specified interval `(min, max)` with a higher probability.\n\n    ```code\n    Gen\u003cLong\u003e gen = LongGen.biased(long min, long max);\n    ```\n\n-   **Unbounded Long Arbitrary Generator**: This generator produces any positive long number with\n    the same probability, following a uniform distribution.\n\n    ```code\n    Gen\u003cLong\u003e gen = LongGen.arbitrary();\n    ```\n\n-   **Bounded Long Arbitrary Generator**: Similar to the unbounded version, this generator produces\n    long integers between `min` and `max` (inclusive) with the same probability, following a uniform\n    distribution.\n\n    ```code\n    Gen\u003cLong\u003e gen = LongGen.arbitrary(long min, long max);\n    ```\n\n#### **Double Generators**\n\n-   **Unbounded Double Biased Generator**: This generator focuses on producing specific double\n    values with higher probability. It generates values such as zero, `Byte.MAX_VALUE`,\n    `Byte.MIN_VALUE`, `Short.MAX_VALUE`, `Short.MIN_VALUE`, `Integer.MAX_VALUE`,\n    `Integer.MIN_VALUE`, `Long.MIN_VALUE`, `Long.MAX_VALUE`, `Double.MIN_VALUE`, and\n    `Double.MAX_VALUE`.\n\n    ```code\n    Gen\u003cDouble\u003e gen = DoubleGen.biased();\n    ```\n\n-   **Bounded Double-Biased Generator**: Similar to the unbounded version, this generator produces\n    values within the specified interval `(min, max)` with a higher probability.\n\n    ```code\n    Gen\u003cDouble\u003e gen = DoubleGen.biased(double min, double max);\n    ```\n\n-   **Unbounded Double Arbitrary Generator**: This generator produces any double number with the\n    same probability, following a uniform distribution.\n\n    ```code\n    Gen\u003cDouble\u003e gen = DoubleGen.arbitrary();\n    ```\n\n-   **Bounded Double Arbitrary Generator**: Similar to the unbounded version, this generator\n    produces double numbers between `min` and `max` (inclusive) with the same probability, following\n    a uniform distribution.\n\n    ```code\n    Gen\u003cDouble\u003e gen = DoubleGen.arbitrary(double min, double max);\n    ```\n\n#### **Big Integer and Big Decimal Generators**\n\n-   **Unbounded BigInteger Biased Generator**: This generator focuses on producing specific big\n    integer values with higher probability: `Integer.MAX_VALUE + 1`, `Integer.MIN_VALUE -1`,\n    `Long.MIN_VALUE - 1`, `Long.MAX_VALUE + 1`, and zero. It also produces big integer numbers of 64\n    bits uniformly distributed.\n\n    ```code\n    Gen\u003cBigInteger\u003e gen = BigIntGen.biased();\n    ```\n\n-   **Bounded BigInteger-Biased Generator**: Similar to the unbounded version, this generator\n    produces values within the specified interval `(min, max)` with a higher probability.\n\n    ```code\n    Gen\u003cBigInteger\u003e gen = BigIntGen.biased(BigInteger min, BigInteger max);\n    ```\n\n-   **Unbounded BigInteger Arbitrary Generator**: This generator produces any big integer number of\n    64 bits with the same probability, following a uniform distribution.\n\n    ```code\n    Gen\u003cBigInteger\u003e gen = BigIntGen.arbitrary();\n    ```\n\n-   **Bounded BigInteger Arbitrary Generator**: Similar to the unbounded version, this generator\n    produces big integer numbers between `min` and `max` (inclusive) with the same probability,\n    following a uniform distribution.\n\n    ```code\n    Gen\u003cBigInteger\u003e gen = BigIntGen.arbitrary(BigInteger min, BigInteger max);\n    ```\n\n-   **Bounded Decimal-Biased Generator**: Similar to the unbounded version, this generator produces\n    values within the specified interval `(min, max)` with a higher probability.\n\n    ```code\n    Gen\u003cBigDecimal\u003e gen = BigDecGen.biased(BigDecimal min, BigDecimal max);\n    ```\n\n-   **Unbounded Decimal Arbitrary Generator**: This generator produces any decimal number with the\n    same probability, following a uniform distribution.\n\n    ```code\n    Gen\u003cBigDecimal\u003e gen = BigDecGen.arbitrary();\n    ```\n\n-   **Bounded Decimal Arbitrary Generator**: Similar to the unbounded version, this generator\n    produces decimal numbers between `min` and `max` (inclusive) with the same probability,\n    following a uniform distribution.\n\n    ```code\n    Gen\u003cBigDecimal\u003e gen = BigDecGen.arbitrary(BigDecimal min, BigDecimal max);\n    ```\n\n#### **Other Primitive Generators**\n\n-   **Byte Generator**: This generator creates byte arrays with lengths biased or uniformly\n    distributed within specified ranges.\n\n```code\n\n    Gen\u003cbyte[]\u003e gen = BytesGen.biased(int minLength, int maxLength);\n    Gen\u003cbyte[]\u003e gen = BytesGen.arbitrary(int minLength, int maxLength);\n\n```\n\n-   **Character Generator**: Various character generators are available, including arbitrary\n    characters, characters within specified ranges, alphanumeric characters, alphabetic characters,\n    letters, digits, and ASCII characters.\n\n    ```code\n    Gen\u003cCharacter\u003e gen = CharGen.arbitrary();\n    Gen\u003cCharacter\u003e gen = CharGen.arbitrary(char min, char max);\n    Gen\u003cCharacter\u003e gen = CharGen.alphanumeric();\n    Gen\u003cCharacter\u003e gen = CharGen.alphabetic();\n    Gen\u003cCharacter\u003e gen = CharGen.letter();\n    Gen\u003cCharacter\u003e gen = CharGen.digit();\n    Gen\u003cCharacter\u003e gen = CharGen.ascii();\n    ```\n\n-   **Boolean Generator**: This generator produces true or false values with equal probability.\n\n    ```code\n    Gen\u003cBoolean\u003e gen = GenBool.arbitrary();\n    ```\n\n-   **Instant Generator**: For generating Instant values, both biased and arbitrary generators are\n    available. The biased generators focus on specific Instant values, while the arbitrary\n    generators provide uniform distribution.\n\n    ```code\n    Gen\u003cInstant\u003e gen = InstantGen.biased();\n    Gen\u003cInstant\u003e gen = InstantGen.biased(long min, long max);\n    Gen\u003cInstant\u003e gen = InstantGen.arbitrary();\n    Gen\u003cInstant\u003e gen = InstantGen.arbitrary(long min, long max);\n    ```\n\nThese comprehensive primitive type generators enable you to perform thorough Property-Based Testing\nwith ease and precision.\n\n### \u003ca name=\"cg\"\u003e\u003ca/\u003e Containers Generators\n\nIn `java-fun`, we provide generators for container types like lists, sets, and maps. These\ngenerators allow you to create diverse test data for various scenarios. Let's explore them in\ndetail:\n\n#### **List Generator**\n\n-   **Bounded List Biased Generator**: This generator produces lists of type `List\u003cT\u003e` with a bias\n    towards specific values. You can specify the minimum and maximum lengths for the generated\n    lists.\n\n    ```code\n    Gen\u003cList\u003cT\u003e\u003e gen = ListGen.biased(Gen\u003cT\u003e gen, int minLength, int maxLength);\n    ```\n\n-   **Bounded List Arbitrary Generator**: Similar to the biased generator, this generator produces\n    lists of type `List\u003cT\u003e` within the specified length range. However, all values within this range\n    are generated with equal probability.\n\n    ```code\n    Gen\u003cList\u003cT\u003e\u003e gen = ListGen.arbitrary(Gen\u003cT\u003e gen, int minLength, int maxLength);\n    ```\n\n-   **List of N Generator**: This generator creates lists of type `List\u003cT\u003e` with a fixed size `size`\n    using the provided generator `gen`.\n\n    ```code\n    Gen\u003cList\u003cT\u003e\u003e gen = ListGen.ofN(Gen\u003cT\u003e gen, int size);\n    ```\n\n#### **Set Generator**\n\n-   **Set of N Generator**: This generator creates sets of type `Set\u003cT\u003e` with a fixed size `size`\n    using the provided generator `gen`. If the generator cannot produce enough distinct elements, it\n    will fail after 10 times the specified size.\n\n    ```code\n    Gen\u003cSet\u003cT\u003e\u003e gen = SetGen.ofN(Gen\u003cT\u003e gen, int size);\n    ```\n\n    You can also create a new generator with a different number of tries using the method\n    `withMaxTries`.\n\n#### **Map Generator**\n\n-   **Map Generator**: This generator creates maps of type `Map\u003cK, V\u003e` with a specified size `size`.\n    You need to provide key and value generators, and it ensures that keys are distinct. If the key\n    generator cannot produce enough distinct keys, it will fail after 10 times the specified size\n    (customized with the method `withMaxTries`)\n\n    ```code\n    Gen\u003cMap\u003cK, V\u003e\u003e gen = MapGen.of(Gen\u003cK\u003e keyGen, Gen\u003cV\u003e valueGen, int size);\n    ```\n\n### \u003ca name=\"trg\"\u003e\u003ca/\u003e Tuples and Record Generators\n\n`java-fun` also provides generators for tuples and record-like structures.\n\n#### **Pair Generator**\n\n-   **Pair Generator**: This generator creates pairs of type `Pair\u003cA, B\u003e` using the provided\n    generators for elements `A` and `B`.\n\n    ```code\n    Gen\u003cPair\u003cA, B\u003e\u003e gen = PairGen.of(Gen\u003cA\u003e _1, Gen\u003cB\u003e _2);\n    ```\n\n#### **Triple Generator**\n\n-   **Triple Generator**: This generator creates triples of type `Triple\u003cA, B, C\u003e` using the\n    provided generators for elements `A`, `B`, and `C`.\n\n    ```code\n    Gen\u003cTriple\u003cA, B, C\u003e\u003e gen = TripleGen.of(Gen\u003cA\u003e _1, Gen\u003cB\u003e _2, Gen\u003cC\u003e _3);\n    ```\n\n#### **Record Generator**\n\n-   **Record Generator**: A record is a structured data type with named fields and their associated\n    values. In `java-fun`, you can create record-like structures using the `MyRecordGen` generator. This\n    generator allows you to define fields and associated generators, making it easy to generate\n    structured data.\n\n    ```code\n    Gen\u003cMyRecord\u003e person = MyRecordGen.of(name, StrGen.arbitrary(1, 20),\n                                          age, IntGen.biased(0, 150),\n                                          birthdate, InstantGen.arbitrary()\n                                         );\n    ```\n\n    The `fun.gen.MyRecordGen` class functions as a `Map\u003cString, ?\u003e` to store generated data and provides\n    convenient access to field values without requiring explicit type conversions. Record generators\n    are highly versatile and useful for creating custom object generators using the function map.\n    Find an example [here](#og).\n\n### \u003ca name=\"com\"\u003e\u003ca/\u003e Combinators\n\n`java-fun` offers various combinator functions to enhance the capabilities of generators.\n\n-   **OneOf Combinator**: The `oneOf` combinator selects one generator from a list of generators.\n    All generators in the list have the same probability of being chosen, and they operate\n    independently.\n\n    ```code\n    Gen\u003cA\u003e gen = Combinators.oneOf(Gen\u003c? extends A\u003e gen, Gen\u003c? extends A\u003e... others);\n    ```\n\n    Alternatively, you can pass a list of values instead of generators:\n\n    ```code\n    Gen\u003cA\u003e gen = Combinators.oneOf(List\u003cA\u003e values);\n    ```\n\n    Or use varargs:\n\n    ```code\n    Gen\u003cA\u003e gen = Combinators.oneOf(A value, A... others);\n    ```\n\n-   **Freq Combinator**: The `freq` combinator is similar to `oneOf`, but it allows you to assign\n    different weights (probabilities) to each generator, controlling their chances of being\n    selected.\n\n    ```code\n    Combinators.freq(Pair\u003cInteger, Gen\u003c? extends A\u003e\u003e freq, Pair\u003cInteger, Gen\u003c? extends A\u003e\u003e... others);\n    ```\n\n    For example, you can generate strings with a 75% probability and booleans with a 25%\n    probability:\n\n    ```code\n    Combinators.freq(Pair.of(3, StrGen.biased(0, 10)), Pair.of(1, BoolGen.arbitrary()));\n    ```\n\n-   **Nullable Combinator** : The `nullable` combinator introduces the possibility of generating\n    `null` values in your data. It is particularly useful for catching potential\n    `NullPointerException` issues.\n\n    ```code\n     Combinators.nullable(Gen\u003cO\u003e gen)\n\n     //custom probability for null values\n     Combinators.nullable(Gen\u003cO\u003e gen,\n                          int prob)\n    ```\n\n-   **nOf Combinator**: pick n elements randomly from a list or set.\n\n    ```code\n     Gen\u003cList\u003cO\u003e\u003e nListGen =  Combinators.nOf(List\u003cO\u003e list,\n                                              int n);\n\n     Gen\u003cSet\u003cT\u003e\u003e nSetGen =  Combinators.nOf(Set\u003cO\u003e set,\n                                            int n)\n    ```\n\n-   **subsets and combinations Combinator**: They generate all possible subsets and n-combinations.\n\n    ```code\n     Gen\u003cList\u003cO\u003e\u003e comb = Combinators.combinations(int n,\n                                                  List\u003cO\u003e set);\n\n     Gen\u003cSet\u003cO\u003e\u003e subsets =  Combinators.subsets(Set\u003cO\u003e set)\n    ```\n\n-   **shuffle Combinator**:\n\n    ```code\n     Gen\u003cList\u003cO\u003e\u003e gen = Combinators.shuffle(List\u003cO\u003e list);\n\n    ```\n\n### \u003ca name=\"og\"\u003e\u003ca/\u003e Objects generators\n\nThis section on \"Objects Generators\" explains how to create generators for custom objects in your\nmodel using `MyRecordGen` and the function map. Let's delve deeper into this process:\n\nConsider you have a class `User` with fields `login`, `name`, and `password`. You want to generate\ninstances of this class with various values for testing purposes.\n\n```code\npublic class User {\n    String login;\n    String name;\n    String password;\n\n    public User(String login, String name, String password) {\n        this.login = login;\n        this.name = name;\n        this.password = password;\n    }\n\n    // Additional methods like toString, equals, hashCode, getters, etc.\n}\n```\n\nNow, you can create a generator for the `User` class as follows:\n\n1. Define generators for the individual fields of the `User` class, such as `login`, `password`, and\n   `name`. These generators determine the values of each field.\n\n    ```code\n    Gen\u003cString\u003e loginGen = StrGen.alphabetic(0, 100);\n    Gen\u003cString\u003e passwordGen = StrGen.biased(0, 100);\n    Gen\u003cString\u003e nameGen = StrGen.alphabetic(0, 100);\n    ```\n\n2. Create a `User` generator using `MyRecordGen`:\n\n    ```code\n    Gen\u003cUser\u003e userGen = MyRecordGen.of(\"login\", loginGen,\n                                       \"name\", nameGen,\n                                       \"password\", passwordGen)\n                                   .map(record -\u003e new User(record.getStr(\"login\").orElse(null),\n                                                           record.getStr(\"name\").orElse(null),\n                                                           record.getStr(\"password\").orElse(null)\n                                                          )\n                                       );\n    ```\n\n    Here's how this works:\n\n    - `MyRecordGen.of(\"login\", loginGen, ...)` defines a record generator with fields \"login,\" \"name,\"\n      and \"password,\" each associated with their respective generators.\n\n    - `.map(record -\u003e new User(...))` uses the function map to transform the generated record into a\n      `User` object. The `record` object allows you to access the generated values for each field\n      using methods like `getStr(\"fieldName\")`.\n\n    - `orElse(null)` ensures that if a value for a field is not generated (which can happen with\n      optional values), it defaults to `null`.\n\nWith this `userGen`, you can now generate instances of the `User` class with random or predefined\nvalues for testing. This approach allows you to easily create generators for complex objects in your\nmodel, making property-based testing more effective and efficient.\n\n### \u003ca name=\"rg\"\u003e\u003ca/\u003e Recursive generators\n\n`NamedGen` provides a simple and intuitive way to define recursive generators, allowing you to\ncreate complex data structures with ease. This is particularly useful when generating data\nstructures that reference themselves or include nested structures.\n\n#### Example:\n\n```code\n\nGen\u003cMyRecord\u003e recordGen =\n    NamedGen.of(\"person\",\n                MyRecordGen.of(\"age\", IntGen.arbitrary(16, 100),\n                               \"name\", StrGen.alphabetic(10, 50),\n                               \"father\", NamedGen.of(\"person\")\n                              )\n                           .withOptKeys(\"father\")\n                );\n\n// Generate and print 10 sample records\nrecordGen.sample(10).forEach(System.out::println);\n```\n\nIn this example, we create a generator for a `person` record that includes fields for `age`, `name`,\nand a potentially recursive field `father`. The use of `NamedGen.of(\"person\")` inside the\n`RecordGen` indicates that the `father` field refers to the same `person` generator, creating a\nrecursive structure.\n\nWith `NamedGen`, defining and using recursive generators becomes straightforward, allowing you to\nmodel complex data relationships effortlessly.\n\n---\n\n### \u003ca name=\"ucp\"\u003e\u003ca/\u003e Useful and common patterns\n\n#### \u003ca name=\"seq\"\u003e\u003ca/\u003e Sequential generators\n\nThe `Gen.seq` method provides a powerful tool for generating sequential data, making it particularly\nuseful for testing scenarios. This method allows you to create generators that produce values based\non the call sequence, making it easy to simulate various scenarios and conditions during testing.\n\n```code\n\nGen\u003cString\u003e seqGen = Gen.seq(n -\u003e \"TestValue_\" + n);\n\nseqGen.sample(5)\n      .forEach(System.out::println);\n\n```\n\nIn this example, the `Gen.seq` method is used to create a generator that produces sequential\nstrings, such as \"TestValue_1,\" \"TestValue_2,\" and so on. This can be incredibly useful when you\nneed to test functionalities that involve ordered or sequential data.\n\nBy incorporating `Gen.seq` into your test data generation, you can enhance the precision and\neffectiveness of your testing strategies, ensuring that your code behaves as expected across various\nscenarios and sequences of data.\n\n#### \u003ca name=\"suchthat\"\u003e\u003ca/\u003e Such-That\n\nThe function `suchThat` takes a predicate and returns a new generator that produces only values that\nsatisfy the condition. For example, let's use this idea to create generators of valid and invalid\ndata:\n\n```code\n\n  //let's create a generator that produces all possible combinations of nullable values\n  Gen\u003cUser\u003e chaosGen = userGen.withAllNullValues()\n\n  Predicate\u003cUser\u003e isValid = user -\u003e\n                                    user.getLogin() != null \u0026\u0026\n                                    user.getPassword() != null \u0026\u0026\n                                    user.getName() != null \u0026\u0026\n                                    !user.getLogin().trim().isEmpty() \u0026\u0026\n                                    !user.getName().trim().isEmpty() \u0026\u0026\n                                    !user.getPassword().trim().isEmpty();\n\n  Gen\u003cUser\u003e validUserGen = chaosGen.suchThat(isValid);\n\n  Gen\u003cUser\u003e invalidUserGen = chaosGen.suchThat(isValid.negate());\n\n\n```\n\n#### \u003ca name=\"flatmap\"\u003e\u003ca/\u003e Flatmap\n\nYou can create new generators from existing ones using the flatmap function. For example, let's\ncreate a set generator where the number of elements is random between 0 and ten.\n\n```code\n\n\nGen\u003cString\u003e elemGen = StrGen.letters(5, 10);\n\nGen\u003cSet\u003cString\u003e\u003e setGen = IntGen.arbitrary(1,10)\n                                .then(size -\u003e  SetGen.of(elemGen,size))\n\n\n```\n\nDo notice that the size is determined at creation time, in other words, all the generated sets will\nhave the same size.\n\n### \u003ca name=\"jmeter\"\u003e\u003ca/\u003e Using generators in JMeter Test Plans (JMX Files)\n\nTo integrate custom generators into your JMeter test plans (JMX files), follow these steps:\n\n1. **Create a JMeter Function:**\n\n    - Develop a JMeter function by extending the `org.apache.jmeter.functions.AbstractFunction`\n      class. Refer to the example class\n      [JMeterExampleGen](../src/test/java/fun/jmeter/JMeterExampleFunction.java) for guidance.\n\n2. **Build a JAR File:**\n\n    - Compile your JMeter functions into a JAR file, e.g., `my-jmeter-functions.jar`. If you are\n      using Maven and your functions are in the `test` folder, generate the JAR with the command\n   \n      ```shell\n      mvn package\n      mvn jar:test-jar\n      ```\n     The resulting JAR will be in the `target` folder.\n\n3. **Place JAR in JMeter's Extension Folder:**\n\n    - Move the JAR file (`my-jmeter-functions.jar`) into the `${JMETER_HOME}/lib/ext` folder.\n\n4. **Include Dependencies:**\n\n    - Download the latest version of the `java-fun` JAR from Maven Central and place it in the\n      `${JMETER_HOME}/lib` folder. Also, include any other dependencies, such as `json-values` if\n      needed, in the same folder.\n\n5. **Restart JMeter:**\n\n    - Restart JMeter to ensure that the new functions and dependencies are recognized.\n\n6. **Verify Function Integration:**\n\n    - Open the function dialog in JMeter.\n      ![JMeter dialog function](./jmeter-open-function-dialog.png)\n    - Select your custom function from the list.\n      ![JMeter select generator](./jmeter-select-generator.png)\n    - Verify that your function is available and can generate data.\n      ![JMeter generates data](./jmeter-gen-example-data.png)\n\n7. **Utilize the Function in HTTP Request Payloads:**\n    - Incorporate your custom function to generate dynamic payloads for HTTP requests.\n      ![JMeter generates data](./jmeter-body-req.png)\n\nBy following these steps, you can seamlessly integrate custom generators into your JMeter test\nplans, enhancing the flexibility and adaptability of your performance tests.\n\n## \u003ca name=\"optics\"\u003e\u003ca/\u003e Optics: Lenses, Optionals, and Prism\n\nNavigating through recursive data structures, such as records and tuples, to locate, insert, or\nmodify data is a common and often challenging task. This process can be error-prone, with the\nconstant risk of encountering NullPointerExceptions, leading to a need for defensive programming\npractices and the inclusion of substantial boilerplate code. The complexity intensifies as the\nstructure becomes more deeply nested. In contrast, imperative programming tends to rely on getters\nand setters, which come with their own inconveniences.\n\nFunctional Programming takes a different approach by utilizing optics to address these challenges\neffectively. Before delving into the specifics of optics and their implementation in **java-fun**,\nit's essential to understand Algebraic Data Types (ADTs).\n\nIn essence, a type serves as a label for a set of values. Unlike objects, types lack inherent\nbehavior. Instead, we can perform operations on types. Consider types A and B, each with its\nrespective domains:\n\n```code\nA = { \"a\", \"b\" }\nB = { 1, 2, 3 }\n```\n\nIt is possible to create new types by combining A and B, resulting in a tuple with two elements:\n\n```code\nT = ( A, B )\nT = [ (\"a\", 1), (\"a\", 2), (\"a\", 3), (\"b\", 1), (\"b\", 2), (\"b\", 3) ]\n```\n\nThe order of combination matters; switching the order of (B, A) would yield a distinct type. These\ncombinations are known as product-types, resulting in six possible values.\n\nAlternatively, we can group A and B into fields to form a record:\n\n```code\nR = { f: A,  f1: B }\nR = [\n{ f:\"a\", f1:1}, { f:\"a\", f1:2}, { f:\"a\", f1:3}, { f:\"b\", f1:1},\n{ f:\"b\", f1:2}, { f:\"b\", f1:3}\n]\n```\n\nIn this case, the order of the fields becomes irrelevant. Records represent another class of\nproduct-types, offering the same six possible values. Notably, Java introduced records in\nrelease 14.\n\nAdditionally, we can combine A and B into a sum-type:\n\n```code\nS = A | B\nS= [ \"a\", \"b\", 1, 2, 3 ]\n```\n\nThis type has 2 + 3 possible values, where a sum-type represents a choice between multiple potential\noptions. In simpler terms, S can either be A or B.\n\nWithin Functional Programming, optics play a crucial role in handling ADTs. These optics come in\nvarious forms, with \\* _Lenses** and **Optionals\\*\\* (distinct from the Java Optional class) being\nparticularly suited for product-types, while _ \\*Prisms \\*\\* assist in dealing with sum-types.\nOptics offer a powerful means of separating concerns and simplifying operations in such complex data\nstructures.\n\nIt's essential to clarify several key concepts:\n\n-   **Action**: An action refers to a function responsible for executing operations on the focus of\n    a path. The most significant actions include _get_, _set_, and _modify_.\n\n-   **Path**: The path specifies which data to focus on and where to locate it within the structure.\n\n-   **Structure**: The structure represents the chunk of data that we intend to work with. The path\n    selects specific data from within this structure, and that data is then passed to the action.\n\n-   **Focus**: The focus is the smaller piece of the structure indicated by the path. This focus is\n    what the action operates on.\n\nA **Lens ** functions by zooming in on a particular piece of data within a larger structure.\nImportantly, a Lens must never fail when attempting to get or modify its focus. On the other hand,\nan **Optional** is another optic similar to a Lens, with the key distinction that the focus may not\nnecessarily exist.\n\nTo illustrate these concepts, let's use the following records:\n\n```code\npublic record Person(String name, Address address, Integer ranking) {\n\n    public Person {\n        if(name == null || name.isBlank())\n            throw new IllegalArgumentException(\"name empty\");\n\n        if(address == null)\n            throw new IllegalArgumentException(\"address empty\");\n    }\n\n}\n\npublic record Address(Coordinates coordinates, String description) {\n\n    public Address {\n        if(coordinates == null \u0026\u0026 description == null)\n            throw new IllegalArgumentException(\"invalid address\");\n    }\n\n}\n\npublic record Coordinates(double longitude, double latitude) {\n\n    public Coordinates {\n        if(longitude \u003c -180 || longitude \u003e 180)\n            throw new IllegalArgumentException(\"180 =\u003e longitude \u003e= -180\");\n\n        if(latitude \u003c -90 || latitude \u003e 90)\n            throw new IllegalArgumentException(\"90 =\u003e latitude \u003e= -90\");\n    }\n}\n```\n\nNow, let's create some optics using json-fun:\n\n```code\n// Person represents the entire structure, while Address is the focus, which is a required field according to the Person constructor.\nLens\u003cPerson, Address\u003e addressLens =\n     new Lens\u003c\u003e(Person::address,\n                address -\u003e person -\u003e new Person(person.name(),\n                                                address,\n                                                person.birthDate()));\n\n\n// Person is the entire structure, and the String representing the name is the focus, which is also a required field.\nLens\u003cPerson, String\u003e nameLens =\n     new Lens\u003c\u003e(Person::name,\n                name -\u003e person -\u003e new Person(name,\n                                             person.address(),\n                                             person.birthDate()));\n\n// Person is the entire structure, and the Integer representing the ranking is the focus. It's not required, so we use an optional instead of a lens.\nOption\u003cPerson, Integer\u003e rankingOpt =\n     new Option\u003c\u003e(person -\u003e Optional.ofNullable(person.ranking()),\n                  ranking -\u003e person -\u003e new Person(person.name(),\n                                                  person.address(),\n                                                  ranking));\n```\n\nAs you can observe, creating a Lens or an Optional simply involves defining the _get_ and _set_\nactions. In lenses, the `get` action returns the focus, whereas in optionals, it returns the focus\nwrapped in a Java Optional, acknowledging the possibility that it may not exist. In java-fun, the\noptional optic is known as an `Option`.\n\nRegarding the modify action, it is generated internally based on _get_ and _set_. It's worth noting\nthat defining the _set_ action may reveal the challenges of working with records. Records are\nimmutable data structures, so every modification necessitates creating a new instance. This\ncomplexity is magnified when dealing with nested records. However, we will explore how composing\noptics can simplify this process.\n\nLet's delve into the fundamental actions of a lens and an optional, with the context of the whole\nstructure (S) and the focus (F) in mind:\n\n```code\nget :: Function\u003cS, F\u003e            // for lenses\nget :: Function\u003cS, Optional\u003cF\u003e\u003e  // for optionals\nset :: Function\u003cF, Function\u003cS, S\u003e\u003e\nmodify :: Function\u003cFunction\u003cF, F\u003e, Function\u003cS, S\u003e\u003e\n```\n\nConsider the focus to be the name of a person. The get action is a function that, given a person,\nreturns their name. The set action, on the other hand, is a function that takes a new name and\nreturns a function. This returned function, when applied to a person, produces a new person with the\nupdated name. In essence, `set` allows us to replace the name with a new one.\n\nNow, let's explore these actions through a practical example:\n\n```code\nPerson joe = new Person(\"Joe\", address, null);\n\n// Setting a new name using the set action\nPerson joeArmstrong = nameLens.set.apply(\"Joe Armstrong\").apply(joe);\n\n// Records are immutable, so we create a new person with the updated name\nAssertions.assertEquals(\"Joe\", nameLens.get.apply(joe));\nAssertions.assertEquals(\"Joe Armstrong\", nameLens.get.apply(joeArmstrong));\n\n// Define a function to convert a name to uppercase\nFunction\u003cString, String\u003e toUpper = String::toUpperCase;\n\n// Use the modify action to apply the toUpper function to the name\nPerson joeUpper = nameLens.modify.apply(toUpper).apply(joe);\n\n// Check if the modification resulted in uppercase name\nAssertions.assertEquals(\"JOE\", nameLens.get.apply(updated));\n\n// Let's increment the ranking by one\n// Since ranking is null, the same person is returned\nAssertions.assertEquals(joe, rankingOpt.modify.apply(ranking -\u003e ranking + 1).apply(joe))\n\n// Set a new ranking value (1)\nPerson joeRanked = rankingOpt.set(1).apply(joe);\n\n// Verify if the ranking was set correctly\nAssertions.assertEquals(1, joeRanked.ranking())\n\n// Since ranking is 1, the function is applied, and a new person is created with ranking * 10\nPerson joeRankedUpdated = rankingOpt.modify.apply(ranking -\u003e ranking * 10).apply(joeRanked);\n\n// Check if the ranking was modified as expected\nAssertions.assertEquals(10, joeRankedUpdated.ranking())\n```\n\nIt's essential to note that in functional programming, we follow a different approach compared to\nobject-oriented programming (OOP). Instead of starting with a person object and then using getters\nor setters, we first define actions and potentially compose them. Only in the end do we specify the\ninputs and execute these actions. This functional approach provides a clear and systematic way of\nworking with data.\n\nFurthermore, lenses adhere to two important laws:\n\n1. **getSet Law**: This law states that if you get a value and set it back in, the result should be\n   a value identical to the original one. In other words, setting a value and then getting it should\n   not change the underlying data.\n\n2. **setGet Law**: According to this law, if you set a value, you should always get the same value.\n   This ensures that the set action accurately updates a value inside the container without altering\n   other aspects. These laws are significant in functional programming as they enhance code clarity\n   and reasoning.\n\nLet's transition our discussion to Prisms. The concept of a Prism can be likened to what happens\nwhen light passes through it, but in the context of sum-types, where we have various subtypes to\nconsider, and our goal is to focus on a specific one. Let's create a Prism to illustrate this:\n\n```code\nPrism\u003cException, RuntimeException\u003e prism =\n    new Prism\u003c\u003e(e -\u003e {\n        if (e instanceof RuntimeException) return Optional.of(((RuntimeException) e));\n        return Optional.empty();\n    },\n    r -\u003e r);\n```\n\nTo create a Prism, you need to define two functions. The first function outlines how to transition\nfrom the generic type Exception to the specific subtype RuntimeException. Conversely, the second\nfunction defines the reverse transition, as a RuntimeException is also an Exception, so we return it\ndirectly.\n\nHowever, Prisms can also be used to extract a subset of values from a more general set based on\nspecific criteria. For example, let's create a Prism to extract all strings that represent integer\nnumbers:\n\n```code\nPrism\u003cString, Integer\u003e intPrism =\n    new Prism\u003c\u003e(str -\u003e {\n        try {\n            return Optional.of(Integer.parseInt(str));\n        }\n        catch (NumberFormatException e) {\n            return Optional.empty();\n        }\n    },\n    integer -\u003e Integer.toString(integer)\n);\n```\n\nIn the above Prism, the first function converts from String to Integer. It can fail if the input\nstring is not a valid integer, resulting in an empty Optional. The second function, on the other\nhand, converts from Integer to String and never fails since every integer has a string\nrepresentation.\n\nLet's examine the key actions and their signatures for the intPrism:\n\n```code\ngetOptional :: Function\u003cString, Optional\u003cInteger\u003e\u003e\n\nmodify :: Function\u003cFunction\u003cInteger, Integer\u003e, Function\u003cString, String\u003e\u003e\n\nmodifyOpt :: Function\u003cFunction\u003cInteger, Integer\u003e, Function\u003cString, Optional\u003cString\u003e\u003e\u003e\n```\n\nThe getOptional function takes a String as input and returns an Optional. If the input string is not\na number, it returns an Optional.empty. However, if the input is a number, it returns the value\nwrapped in an Optional. In essence, it handles the potential failure of the conversion.\n\nThe modify function is quite practical and takes a function to map numbers. It returns a function\nthat goes from String to String. If the input value is a number, it applies the mapping function and\nreturns the result as a String. However, if the input is not a number, the mapping function cannot\nbe applied, and the original input string is returned as is. Notably, this function doesn't concern\nitself with the success or failure of the operation.\n\nIf you require information about the success of the operation, you can use the modifyOpt action. It\nbehaves similarly to modify but returns an empty Optional when the mapping function cannot be\napplied.\n\nLet's demonstrate these actions with some examples:\n\n```code\nAssertions.assertEquals(Optional.of(\"10\"),\n                        intPrism.getOptional.apply(\"10\"));\n\n// If \"apple\" is not a valid string representation of a number, an empty Optional is returned\nAssertions.assertEquals(Optional.empty(),\n                        intPrism.getOptional.apply(\"apple\"));\n\n// Applying a mapping function to the valid input \"1\" results in \"10\"\nAssertions.assertEquals(\"10\",\n                        intPrism.modify.apply(a -\u003e a * 10)\n                                      .apply(\"1\"));\n\n// Since \"apple\" is not a valid number, the original input \"apple\" is returned\nAssertions.assertEquals(\"apple\",\n                        intPrism.modify.apply(a -\u003e a * 10)\n                                       .apply(\"apple\"));\n```\n\nThese examples showcase the functionality of Prisms and how they handle conversions and potential\nfailures, providing a powerful tool for working with sum-types in functional programming.\n\nYou can combine lenses, optionals, and prisms to build more complex operations. For instance:\n\n```code\n\nOption\u003cAddress, Coordinates\u003e coordinatesOpt =\n    new Option\u003c\u003e(address -\u003e Optional.ofNullable(address.coordinates()),\n                 coordinates -\u003e address -\u003e new Address(coordinates,\n                                                       address.description()));\n\nLens\u003cCoordinates, Double\u003e longitudeLens =\n    new Option\u003c\u003e(Coordinates::longitude,\n                 coordinates -\u003e lon -\u003e new Coordinates(lon, coordinates.latitude()));\n\nLens\u003cCoordinates, Double\u003e latitudeLens =\n    new Option\u003c\u003e(Coordinates::latitude,\n                 coordinates -\u003e lat -\u003e new Coordinates(coordinates.longitude(), lat));\n\n// Let's apply composition!\n\nOption\u003cPerson, Double\u003e personLatitudeOpt =\n    addressLens.compose(coordinatesOpt).compose(latitudeLens);\n\nOption\u003cPerson, Double\u003e personLongitudeOpt =\n    addressLens.compose(coordinatesOpt).compose(longitudeLens);\n```\n\nComposition is a powerful technique to manage complexity. Imagine you need to create a function to\nset both the latitude and longitude of a person:\n\n```code\n\nFunction\u003cCoordinates, Function\u003cPerson, Person\u003e\u003e setCoordinates =\n    c -\u003e personLatitudeOpt.set.apply(c.latitude())\n                           .andThen(personLongitudeOpt.set.apply(c.longitude()));\n\nPerson newPerson = setCoordinates.apply(new Coordinates(14.5, 45.78))\n                                 .apply(person);\n\n```\n\nThis composition allows you to efficiently manipulate complex data structures, such as setting the\nlatitude and longitude of a person with ease.\n\n## \u003ca name=\"inst\"\u003e\u003ca/\u003e Installation\n\nTo include java-fun in your project, add the corresponding dependency to your build tool based on\nyour Java version:\n\nFor Java 8 or higher:\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.imrafaelmerino\u003c/groupId\u003e\n    \u003cartifactId\u003ejava-fun\u003c/artifactId\u003e\n    \u003cversion\u003e1.4.0\u003c/version\u003e\n\u003c/dependency\u003e\n\n```\n\nFor Java 17 or higher:\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.imrafaelmerino\u003c/groupId\u003e\n    \u003cartifactId\u003ejava-fun\u003c/artifactId\u003e\n    \u003cversion\u003e2.2.0\u003c/version\u003e\n\u003c/dependency\u003e\n\n```\n\nFor Java 21 or higher:\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.imrafaelmerino\u003c/groupId\u003e\n    \u003cartifactId\u003ejava-fun\u003c/artifactId\u003e\n    \u003cversion\u003e3.0.0\u003c/version\u003e\n\u003c/dependency\u003e\n\n```\n\nChoose the appropriate version according to your Java runtime.\n\nFind [here](./../docs/CHANGELOG.md) the releases notes.\n\n## \u003ca name=\"rp\"\u003e\u003ca/\u003e Related projects\n\n[json-values](https://github.com/imrafaelmerino/json-values) has defined a JSON generator and some\noptics to manipulate JSON using this library.\n\n[jio-test](https://github.com/imrafaelmerino/JIO?tab=readme-ov-file#jio-test) uses java-fun for its\nProperty-Based-Testing API\n","funding_links":["https://www.buymeacoffee.com/imrafaelmerino"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimrafaelmerino%2Fjava-fun","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimrafaelmerino%2Fjava-fun","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimrafaelmerino%2Fjava-fun/lists"}