{"id":22351426,"url":"https://github.com/metio/reguloj","last_synced_at":"2025-07-30T07:31:43.874Z","repository":{"id":10888772,"uuid":"13179688","full_name":"metio/reguloj","owner":"metio","description":"Lightweight business rule engine","archived":false,"fork":false,"pushed_at":"2024-02-07T01:40:38.000Z","size":422,"stargazers_count":26,"open_issues_count":0,"forks_count":5,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-06-07T05:35:50.556Z","etag":null,"topics":["java","rule-engine"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"0bsd","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/metio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":"AUTHORS.md","dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2013-09-28T19:49:52.000Z","updated_at":"2025-06-01T05:00:39.000Z","dependencies_parsed_at":"2023-11-07T05:08:07.416Z","dependency_job_id":"1bba3681-97b0-4ee1-a0df-a16bab9ff6df","html_url":"https://github.com/metio/reguloj","commit_stats":null,"previous_names":["sebhoss/reguloj"],"tags_count":45,"template":false,"template_full_name":null,"purl":"pkg:github/metio/reguloj","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metio%2Freguloj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metio%2Freguloj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metio%2Freguloj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metio%2Freguloj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/metio","download_url":"https://codeload.github.com/metio/reguloj/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metio%2Freguloj/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266296762,"owners_count":23907012,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-21T11:47:31.412Z","response_time":64,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"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":["java","rule-engine"],"created_at":"2024-12-04T12:13:57.582Z","updated_at":"2025-07-30T07:31:43.600Z","avatar_url":"https://github.com/metio.png","language":"Java","funding_links":[],"categories":["java"],"sub_categories":[],"readme":"\u003c!--\nSPDX-FileCopyrightText: The reguloj Authors\nSPDX-License-Identifier: 0BSD\n --\u003e\n\n# reguloj [![Chat](https://img.shields.io/badge/matrix-%23talk.metio:matrix.org-brightgreen.svg?style=social\u0026label=Matrix)](https://matrix.to/#/#talk.metio:matrix.org)\n\n`reguloj` is a small and lightweight Java rule engine.\n\n## Usage\n\n### Creating rule engines\n\nA rule engine evaluates a set of rules in a specific context. The `RuleEngine` interface offers 3 factory methods to build rule engines:\n\n```java\n// All rules will be evaluated indefinitely until no further rule fires.\nRuleEngine\u003cCONTEXT\u003e chained = RuleEngine.chained();\n\n// All rules will be evaluated, but only a maximum number of 5 times.\nRuleEngine\u003cCONTEXT\u003e limited = RuleEngine.limited(5);\n\n// Evaluates all rules, stops after the first one that fires.\nRuleEngine\u003cCONTEXT\u003e firstWins = RuleEngine.firstWins();\n```\n\nAll provided rule engines are thread-safe and can be used as often as you like. If custom inference behavior is required, subclass `AbstractRuleEngine` and implement the `infer()` method. The following code example shows how to work with rule engines:\n\n```java\n// setup - more details later\nRuleEngine\u003cCONTEXT\u003e engine = ...;\nCollection\u003cRule\u003cCONTEXT\u003e\u003e rules = ...;\nCONTEXT context = ...;\n\n// true if at least one rule can fired.\nengine.analyze(rules, context);\n\n// perform conclusions of those rules that fired.\nengine.infer(rules, context);\n```\n\nNote that the order of the collection dictates the evaluation order of your rules - if order does matter, use `List` rather than `Set` as a `Collection` implementation.\n\n### Creating rules\n\nA [rule](https://github.com/metio/reguloj/blob/main/src/main/java/wtf/metio/reguloj/Rule.java) runs in a given context. Additionally, it can be checked whether a rule fires in a given context.\n\nEither implement the `Rule` interface yourself and or use the supplied rule implementation and builder. A standard rule is composed of a `java.util.function.Predicate` and `java.util.function.Consumer`. Both interfaces require you to implement only a single method and do not restrict you in any way. Complex rules can be created by grouping or chaining predicates/consumers together with the help of several utility methods. The following example creates a rule composed of 2 predicates and 2 consumers:\n\n```java\nRule\u003cCONTEXT\u003e rule = Rule.when(predicate1.and(predicate2))\n                .then(consumer1.andThen(consumer2));\n\n// true if the rule would fire in the given context, e.g. the above predicate is true.\nrule.fires(context);\n\n// runs (applies) the rule in the given context\nrule.run(context);\n```\n\nUsing Java 8 lambdas is possible as well, however be aware that some additional type information is required in this case:\n\n```java\nRule\u003cCONTEXT\u003e rule = Rule.\u003cCONTEXT\u003ewhen(context -\u003e context.check())\n                .then(context -\u003e context.action())\n\nRule\u003cCONTEXT\u003e rule = Rule.when((CONTEXT context) -\u003e context.check())\n                .then(context -\u003e context.action())\n```\n\nIn case you want to create a `Rule` that always fires/runs, use the following shortcut:\n\n```java\n// using predefined consumer\nRule\u003cCONTEXT\u003e rule = Rule.always(consumer1.andThen(consumer2));\n\n// using lambda\nRule\u003cCONTEXT\u003e rule = Rule.always((CONTEXT context) -\u003e context.action())\n```\n\nNote that custom implementations of the `Rule` interface don't necessary have to use the `java.util.function` package and are free to choose how their implementation looks like.\n\n### Creating an inference context\n\nAn inference [context](https://github.com/metio/reguloj/blob/main/src/main/java/wtf/metio/reguloj/Context.java) contains information needed by predicates and/or consumers. This project supplies a simple implementation of the Context interface called `SimpleContext` which just wraps a given topic. The `AbstractContext` class can be used to create subclasses in case your rules need extra information. The API acknowledges this by using `\u003cCONTEXT extends Context\u003c?\u003e\u003e` as type parameter for all methods which expect a Context, thus allowing all context implementations to be used. See item 28 in Effective Java for more details.\n\n```java\nCONTEXT context = Context.of(\"some object\");\n```\n\n## Example Use Case\n\nThe [wtf.metio.regoluj.shoppingcart](https://github.com/metio/reguloj/tree/main/src/test/java/wtf/metio/reguloj/shoppingcart) package contains [tests](https://github.com/metio/reguloj/blob/main/src/test/java/wtf/metio/reguloj/shoppingcart/ShoppingCartTest.java) for an example use case revolving around shopping carts, products, and their prices. It works as follows:\n\nWe have a custom `Context` implementation in the form of [wtf.metio.regoluj.shoppingcart.Cart](https://github.com/metio/reguloj/blob/main/src/test/java/wtf/metio/reguloj/shoppingcart/Cart.java) that holds a list of products, and a matching list of prices for those products. The list of products is its main topic. Various `Rules` are used to calculate the price per product in the shopping cart. Written as a `record`, the `Cart` could look like this:\n\n```java\npublic record Cart(List\u003cProduct\u003e topic, List\u003cPrice\u003e prices) implements Context\u003cList\u003cProduct\u003e\u003e {\n\n}\n```\n\nAs you can see, one of the `record` parameters must be named `topic` and use the type of the context in order to correctly implement the method contract of `Context`. Similar, a `Product` and `Price` could look like this:\n\n```java\npublic record Product(String name) {\n\n}\n\npublic record Price(Product product, int price) {\n\n}\n```\n\nThe initial state of a card contains just the products without any previously calculated prices in this example:\n\n```java\nfinal Cart singleProductCart = new Cart(List.of(TEST_PRODUCT), new ArrayList\u003c\u003e());\nfinal Cart multiProductCart = new Cart(List.of(TEST_PRODUCT, TEST_PRODUCT), new ArrayList\u003c\u003e());\n```\n\nThe constant `TEST_PRODUCT` is just some example data that represents objects of your actual business domain: `Product TEST_PRODUCT = new Product(\"xPhone 37\");`.\n\n### Using RuleEngine#firstWins\n\n```java\nRuleEngine\u003cCart\u003e ruleEngine = RuleEngine.firstWins();\n```\n\nWhile using a first-wins `RuleEngine`, our `Rules`s could look like this:\n\n```java\nfinal var standardPrice = Rule\n    .when((Cart cart) -\u003e true) // always fires thus can be used as a fallback\n    .then(cart -\u003e cart.prices().add(new Price(TEST_PRODUCT, 100)));\nfinal var reducedPrice = Rule\n    .when((Cart cart) -\u003e cart.topic().size() \u003e 1) // only fires for multiple products\n    .then(cart -\u003e cart.prices().add(new Price(TEST_PRODUCT, 75 * cart.topic().size())));\n```\n\nAs you can see, we kept the implementation of the rules rather simple, in order to keep the example focused on the `reguloj` related classes. In a real world project, you don't want to specify a constant price for a single product, but rather use some database lookup or similar technique to calculate prices more dynamically. Since we need both a `Context` and a `Collection` of rules, we combine the above into a `List` with:\n\n```java\nCollection\u003cRule\u003cCart\u003e\u003e rules = List.of(reducedPrice, standardPrice);\n```\n\nThe order is important here - we first test if we can apply the reduced priced, and only apply the full price as a fallback. In order to infer a price for our shopping carts, combine `Rules` and `Context` (carts) using the previously built `RuleEngine` as the following example shows:\n\n```java\nruleEngine.infer(rules, singleProductCart);\nruleEngine.infer(rules, multiProductCart);\n```\n\nSince the above rules will only ever add one price, we can check whether everything works as expected like this:\n\n```java\nAssertions.assertEquals(100, singleProductCart.prices().get(0).price())\nAssertions.assertEquals(150, multiProductCart.prices().get(0).price())\n```\n\n### Using RuleEngine#limited\n\n```java\nRuleEngine\u003cCart\u003e ruleEngine = RuleEngine.limited(1);\n```\n\nWhile using a limited `RuleEngine`, our `Rules`s could look like this:\n\n```java\nfinal var standardPrice = Rule\n    .when((Cart cart) -\u003e cart.topic().size() == 1) // fires for single products\n    .then(cart -\u003e cart.prices().add(new Price(TEST_PRODUCT, 100)));\nfinal var reducedPrice = Rule\n    .when((Cart cart) -\u003e cart.topic().size() \u003e 1) // fires for multiple products\n    .then(cart -\u003e cart.prices().add(new Price(TEST_PRODUCT, 75 * cart.topic().size())));\n```\n\nThe difference here is that the first rule only fires for carts that contain a single product (remember the topic of a cart is a list of products) since a limited `RuleEngine` will try ever rule a limited number of times and thus it won't stop after some rule fired as in the first example. Note that this implementation would have worked in the first example as well, however the first example would not work with a limited `RuleEngine`. The implementation for the second rule is exactly the same as the first example.\n\n```java\nCollection\u003cRule\u003cCart\u003e\u003e rules = Set.of(standardPrice, reducedPrice);\n```\n\nSince the order in which rules are fired does not matter, we can use a `Set` rather than  `List`. In case you are planning on creating rules dynamically based on some external data, like XML, YAML, a database, or your neighbours dog, make sure to be a specific as possible in your predicates in order to make your rules as widely usable as possible.\n\n```java\nruleEngine.infer(rules, singleProductCart);\nruleEngine.infer(rules, multiProductCart);\n\nAssertions.assertEquals(100, singleProductCart.prices().get(0).price())\nAssertions.assertEquals(150, multiProductCart.prices().get(0).price())\n```\n\nRunning the inference process is exactly the same no matter which `RuleEngine` you picked or how you `Rule`s are implemented.\n\n### Using RuleEngine#chained\n\n```java\nRuleEngine\u003cCart\u003e ruleEngine = RuleEngine.chained();\n```\n\nWhile using a chained `RuleEngine`, our `Rules`s could look like this:\n\n```java\nfinal var standardPrice = Rule\n        .when((Cart cart) -\u003e cart.topic().size() == 1 \u0026\u0026 cart.prices().size() == 0)\n        .then(cart -\u003e cart.prices().add(new Price(TEST_PRODUCT, 100)));\nfinal var reducedPrice = Rule\n        .when((Cart cart) -\u003e cart.topic().size() \u003e 1 \u0026\u0026 cart.prices().size() == 0)\n        .then(cart -\u003e cart.prices().add(new Price(TEST_PRODUCT, 75 * cart.topic().size())));\n```\n\nSince chained `RuleEngine`s will run all `Rule`s as often as they fire, we need an extra terminal condition to stop re-firing our rules. Since we are only calculating the price of a single product, we can always stop firing our `Rule`s in case there is already a price in our cart.\n\n```java\nCollection\u003cRule\u003cCart\u003e\u003e rules = Set.of(standardPrice, reducedPrice);\n```\n\nAgain, the order of our rules do not matter, thus we are using a `Set`.\n\n```java\nruleEngine.infer(rules, singleProductCart);\nruleEngine.infer(rules, multiProductCart);\n\nAssertions.assertEquals(100, singleProductCart.prices().get(0).price())\nAssertions.assertEquals(150, multiProductCart.prices().get(0).price())\n```\n\nGetting a final price for our carts is exactly the same again.\n\n## Integration\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ewtf.metio.reguloj\u003c/groupId\u003e\n  \u003cartifactId\u003ereguloj\u003c/artifactId\u003e\n  \u003cversion\u003e${version.reguloj}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n```kotlin\ndependencies {\n    implementation(\"wtf.metio.reguloj:reguloj:${version.reguloj}\") {\n        because(\"we want to use a lightweight rule engine\")\n    }\n}\n```\n\nReplace `${version.reguloj}` with the [latest release](https://central.sonatype.com/artifact/wtf.metio.reguloj/reguloj).\n\n## Requirements\n\n| regoluj    | Java |\n|------------|------|\n| 2022.4.12+ | 17+  |\n| 2021.4.13+ | 16+  |\n\n## Alternatives\n\nIn case `reguloj` is not what you are looking for, try these projects:\n\n- [Dredd](https://github.com/amsterdatech/Dredd)\n- [SmartParam](https://github.com/smartparam/smartparam)\n- [ramen](https://github.com/asgarth/ramen)\n- [nomin](https://github.com/dobrynya/nomin)\n- [dvare](https://github.com/dvare/dvare-rules)\n- [ruli](https://github.com/mediavrog/ruli)\n- [MintRules](https://github.com/augusto/MintRules)\n- [Jare](https://github.com/uwegeercken/jare)\n- [tuProlog](http://alice.unibo.it/xwiki/bin/view/Tuprolog/)\n- [drools](https://www.drools.org/)\n- [Easy Rules](https://github.com/j-easy/easy-rules)\n- [n-cube](https://github.com/jdereg/n-cube)\n- [RuleBook](https://github.com/deliveredtechnologies/rulebook)\n- [OpenL Tablets](http://openl-tablets.org/)\n- [JSR 94](https://jcp.org/en/jsr/detail?id=94)\n- [rules](https://github.com/rlangbehn/rules)\n\n## License\n\n```\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetio%2Freguloj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmetio%2Freguloj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetio%2Freguloj/lists"}