{"id":37019077,"url":"https://github.com/commitd/rejux","last_synced_at":"2026-01-14T02:03:01.903Z","repository":{"id":57743027,"uuid":"63315686","full_name":"commitd/rejux","owner":"commitd","description":"Redux in Java","archived":true,"fork":false,"pushed_at":"2016-07-25T19:09:33.000Z","size":89,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-05-01T10:18:38.419Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/commitd.png","metadata":{"files":{"readme":"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}},"created_at":"2016-07-14T08:04:51.000Z","updated_at":"2023-01-28T08:03:02.000Z","dependencies_parsed_at":"2022-09-11T09:41:33.436Z","dependency_job_id":null,"html_url":"https://github.com/commitd/rejux","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/commitd/rejux","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commitd%2Frejux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commitd%2Frejux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commitd%2Frejux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commitd%2Frejux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/commitd","download_url":"https://codeload.github.com/commitd/rejux/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/commitd%2Frejux/sbom","scorecard":{"id":300925,"data":{"date":"2025-08-11","repo":{"name":"github.com/commitd/rejux","commit":"cf52122ae674d8024f56013fc0a533e5bea41379"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.9,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: github.com/commitd/.github/SECURITY.md:1","Info: Found linked content: github.com/commitd/.github/SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: github.com/commitd/.github/SECURITY.md:1","Info: Found text in security policy: github.com/commitd/.github/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T20:36:09.891Z","repository_id":57743027,"created_at":"2025-08-17T20:36:09.891Z","updated_at":"2025-08-17T20:36:09.891Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408711,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","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":[],"created_at":"2026-01-14T02:03:01.115Z","updated_at":"2026-01-14T02:03:01.895Z","avatar_url":"https://github.com/commitd.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rejux\n\n[![Build Status](https://travis-ci.org/commitd/rejux.svg?branch=master)](https://travis-ci.org/commitd/rejux)\n\nA remix of [Redux](http://redux.js.org/) for Java.\n\nWe really like Redux, and [Flux](https://facebook.github.io/flux/) in general, as it makes your application state very easy to reasonable. All your meaningful application state exists in one shared place and that helps decouple components from the data they require.  We aren't going to introduce Redux and Flux here again because they are very well documented on their own sites.\n\nTypically you use the Flux/Redux pattern for UI - specially redux itself is a Javascript library - but we've found it useful for any project where you have a series of components which need to react to global state. As an example we have a web server application with an API to controlling services. We keep the state of the controlled services in a Redux store. How does that help? It means that each API call can reason about what they can and can't do based on the global state. What's the alternative? Each API call would need to ask other services what state they are in and try and piece the puzzle together themselves - that's a lot of duplicated code and spread out logic.\n\nSince we don't tend to use Javascript outside the browser, we recreated our beloved Redux in Java.\n\nBut we've added a few Java twists, since Java isn't Javascript! \n\n## Install \n\nInstall though Maven with:\n\n```\n\u003cdependency\u003e\n  \u003cgroupId\u003esoftware.committed\u003c/groupId\u003e\n  \u003cartifactId\u003erejux\u003c/artifactId\u003e\n  \u003cversion\u003e0.1.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Rejux's interpretation of Redux\n\nRejux has the same primitives as Redux (paraphrasing a lot here, please read the original docs):\n\n* **Actions** - an intent to do something which may change the state\n* **Store** - holds state and allows actions to be dispatched\n* **Reducers** - pure functions which manipulate state based on actions.\n* **Middleware** - preprocess actions prior to reducers (allowing actions to be discarded, changed, or processed independently)\n\nWe have Java interfaces for these concepts which align very closely with Redux concepts. One of the differences is that we also extend from the Java 8 functionals (`Consumer\u003c\u003e`, etc) which makes stream based code much neater.\n\nSo in Rejux Java (see below for more details):\n\n* **Actions** are any `Object` instance (ie you can dispatch an object, there is not specific Action interface to derive from). \n* **Reducers** are as in Redux, but we use a little bit of reflection to make them neater.\n* **Store** is now divided into a `Dispatcher` (interface) and the actual state. Since most applications have non-trival state we jump straight to a 'combining reducer' style approach where we have many states which are combined into a single global (application) state. This, we hope, offers a flexible, type safe and hopefully not tedious way to create global state.\n* **Middleware** is very much like in Redux. We have two places to put middleware which we'll explain in a moment.\n\nWe are now going to talk through each of these in detail. We'll illustrate with a simple example based on a basic calculator. Our operations (eg \"Add 2\")  will be actions and the current result will be kept in a part of the store.\n\n## Actions\n\nActions are really simple.\n\n\n```\n// Zero the result\npublic class ZeroAction {\n\n}\n\n// Add the value to the result\npublic class AddAction {\n  private final int amount;\n  public AddAction(int amount) {\n    this.value = amount;\n  }\n  \n  public int getAmount() {\n    return amount;\n  }\n}\n```\n\nWe suggest you use [Lombok](https://projectlombok.org/), so that last class becomes:\n\n```\n@Data\npublic class AddAction {\n  private final int amount;\n}\n```\n\nwhich is much shorter and nicer.\n\nTo keep things clean and reduce code duplication, we can use inheritance (perhaps used a little over the top here, but it's just an example):\n\n```\npublic interface CalculationAction {\n\n}\n\n@Data\npublic abstract class SingleValueAction implements CalculationAction{\n  private final int amount;\n}\n\n@Data\n@EqualsAndHashcode(callSuper=true)\npublic class AddAction extends SingleValueAction {\n\n}\n\n@Data\n@EqualsAndHashcode(callSuper=true)\npublic class SubtractAction extends SingleValueAction {\n\n}\n\n@Data\n@EqualsAndHashcode(callSuper=true)\npublic class MutiplyAction extends SingleValueAction {\n\n}\n```\n\nThat's actions done. Easy as you can hope for right?\n\n## State\n\nState in Rejux is just a java object. Ideally its immutable but that's your choice - but seriously make it [immutable](https://github.com/andrewoma/dexx).\n\nHere's a bit of state which will store the current result of calculation.\n\n```\n@Data\npublic class Result {\n     private final int value;\n}\n```\n\n## Reducer\n\nOur reducer needs to apply the actions to that state, so we could do this:\n\n```\npublic class ResultReducer implements Reducer\u003cResult\u003e {\n\n        // Sorry - we need this because of Java type erasure...\n        public Class\u003cResult\u003e getType() {\n                return Result.class;\n        }\n\n        public class Result reduce(Result state, Object action) {\n                if(action instanceof AddAction) {\n                        return new Result(state.getValue() + ((AddAction)action).getAmount());\n                } else if(action instanceof ZeroAction) {\n                        return new Result(0);\n                }\n                // No change if we didn't understand the action\n                return state;\n        }\n\n}\n```\n\nBut, lets be honest, that `instanceof` is ugly.... so we have a neater reflection based implementation from `ReflectingReducer\u003c\u003e` too:\n\n```\npublic class ResultReducer extends ReflectingReducer\u003cResult\u003e {\n\n        public ResultReducer() {\n                // Sorry - we need this because of Java type erasure...\n                super(Result.class);\n        }\n\n        public class Result add(Result state, AddAction action) {                \n                return new Result(state.getValue() + action.getAmount());\n        }\n\n        public class Result zero(Result state, ZeroAction action) {\n                return new Result(0);\n        }\n\n}\n```\nThat's done the boring work for us!\n\n## Store \n\nStores and state are a bit different to Redux. That's simply because we want to take advantage of the type safety of Java.\n\nSince Rejux doesn't know about your applications state you need to define it. You do this in an interface:\n\n```\npublic interface CalculatorState {\n        @Reduce(ResultReducer.class)\n        Result result();\n}\n```\n\nIn that one interface you've defined your global state (defined by all the little state classes) and specified which reducer to use.\n\nNow you've defined your global state, how do you turn it into a store and how do you supply the initial values for the state?\n\n```\npublic Store\u003cCalculatorState\u003e newCalculatorState() {\n        \n        CalculatorState initialState = new CalculatorState() {\n                public Result result() {\n                        return new Result(0);\n                }\n\n        };\n\n        return Rejux.createStore(CalculatorState.class, initialState);\n}\n```\n\nYou read that right, it's neat - we provide the initial state via the (type safe) interface which is the application state.\n\nThe `Store\u003c\u003e` interface has `state()` (or `get()`) to get the state and it also has `dispatch(action)` and `subscribe()` to monitor changes.\n\nLets use subscribe to listen to changes in the store, with a quick  example:\n\n```\nStore\u003cCalculatorState\u003e store = newCalculatorStore();\nSubscription subs = store.subscribe((state) -\u003e {\n        System.out.println(\"The store's state changed\");\n        System.out.println(\"Result = \" + state.result().getValue());\n});\n\n// Dispatch an action or two - these will hit our subscriber above:\nstore.dispatch(new AddAction(10));\nstore.dispatch(new ZeroAction(0));\n\n// Now we are done\n\nif(subs.isSubscribed()) {\n  // You don't need to check but we are just showing the Subscription API\n  subs.remove();\n}\n\n```\n\nThere's another trick for your application state: subscribable state.\n\nIn the above example we subscribed to the store. That's ok, but perhaps we are only interested in part of the store, we might want to just monitor that (in a UI component). That's a really good idea in Java as it means you can build reuseable components that are interested in only small parts of the state (ie not dependent on the whole store) \n\nLet's add something to the store to keep track of the history of the calculation.\n\nWhat would our history state class be? Let be really basic and say a list of say Strings which say `\"+2\"`. We want a it to be an immutable list (because that's the right way to do state) so we'll use [Dexx](https://github.com/andrewoma/dexx). Our state is `LinkedList\u003cString\u003e`.\n\nOur reducer now looks like:\n\n```\npublic class HistoryReducer extends ReflectingReducer\u003cLinkedList\u003cString\u003e\u003e {\n\n        public ResultReducer() {\n                // Sorry - we need this because of Java type erasure...\n                super(LinkedList.class);\n        }\n\n        public class LinkedList\u003cString\u003e add(LinkedList\u003cString\u003e state, AddAction action) {                \n                return state.put(\"+\" + action.getAmount()\");\n        }\n\n        public class LinkedList\u003cString\u003e zero(Result state, ZeroAction action) {\n                // clear the history too\n                return new LinkedList\u003cString\u003e();\n        }\n}\n```\n\nNow we enhance our global CalculatorState. Since we want to be able to list just the history we use the `State\u003c\u003e` class as a return type. Just like `Store\u003c\u003e` this has `state()` and `subscribe()` functions (but not `dispatch`).\n\n```\npublic interface CalculatorState {\n        @Reduce(ResultReducer.class)\n        Result result();\n        \n      @Reduce(HistoryReducer.class)\n      State\u003cLInkedList\u003cString\u003e\u003e history();       \n}\n```\n\nFinally how to do create that store again? \n\n```\npublic Store\u003cCalculatorState\u003e newCalculatorState() {\n        \n        CalculatorState initialState = new CalculatorState() {\n                public Result result() {\n                        return new Result(0);\n                }\n\n                public State\u003cLInkedList\u003cString\u003e\u003e history() {\n                        return Rejux.createState(LinkedList.class, new LinkedList\u003cString\u003e());\n                }\n\n        };\n\n        return Rejux.createStore(CalculatorState.class, initialState);\n}\n```\n\nAnd now lets use it by just subscribing to history state changes:\n```\n\nStore\u003cCalculatorState\u003e store = newCalculatorStore();\nSubscription subs = store.state().history().subscribe((state) -\u003e {\n        // NOte that state is of type LinkedList\u003cString\n        System.out.println(\"How did we get here\");\n        state.forEach( s -\u003e System.out.println(\"\\t\"+s));\n});\nstore.dispatch(new AddAction(10));\n\n```\n\n## Middleware\n\nYou can apply middleware in two places: to the global store and to each individual state. You add it as as part of the `Rejux.createStore` and `Rejux.createState` methods as we'll need in a moment.\n\nWe have two pieces of middleware built into the library:\n\n* Filter - provides a simple middleware that allows actions to be discarded. \n* Thunk - provides very useful async like redux-thunk.\n\nWhere you apply which middleware is specific to your application. Typically middleware like thunk will be applied to the global store (`Rejux.createStore`) so its only run once for each action, whereas the filter middlware would likely be applied at the state level (`Rejux.createState`) so its applied to just that state / reducer combination.\n\nWe don't really have any use of these in out calculator, but lets pretend we do and add some middleware:\n\n```\npublic Store\u003cCalculatorState\u003e newCalculatorState() {\n        \n        CalculatorState initialState = new CalculatorState() {\n                public State\u003cResult\u003e result() {\n                        return Rejux.createState(Result.class, new Result(0), new FilterMiddleware((a) -\u003e a instanceof CalculatorAction));\n                }\n\n        };\n\n        return Rejux.createStore(CalculatorState.class, initialState, new ThunkMiddlware());\n}\n```\n\nSo we've filtered out anything which isn't CalculatorAction type from our ResultReducer, and we've got thunk middleware running per action.\n\nTo test thunk lets reset the calculator if the value is already greater than 10. \n\n```\npublic class ResetIfTooBigAction implements ThunkAction\u003cCalculatorState\u003e {\n\n        public void apply(Dispatcher first, CalculatorState state, Object action, Dispatcher next) {\n            // We always allow the result to be zeroed!\n            if(action instanceof ZeroResult) {\n               next.dispatch(action);\n            }\n            \n            if(state.result().value() \u003c 10) {\n              // We aren't too big, so I think the computer will be able to handle this\n              next.dispatch(action);\n            } else {\n              // Oh dear, we are already over the number we can count on our fingers!\n              // Let's zero things and stop this madness!\n              first.dispatch(new ZeroResult());\n            }\n         \n        }\n}\n```\n\nNotice a slight different between Redux and Rejux: In Redux the first arguments are combined as store (Store = State + Dispatcher). We separate out so that the same middleware can be used in all location - the \"first\" dispatcher is the global store dispatcher (ie dispatch into the first part of the pipeline) and the state is either the global store or the sub state.\n\n## Best practise\n\nBased on our experience we've got some recommendations:\n\n* Always use immutable state, you do not want to have bugs where state is accidentally changed outside of dispatch. Our suggestion is to use [Dexx](https://github.com/andrewoma/dexx) or [Immutables](http://immutables.github.io/).\n\n* Think about threading. We've deliberately not dealt with that here because any implementation needs to be aware of the platform way of doing things (Android, Spring, etc). For example on Android we suggest you're subscribers runOnUiThread after dispatch. Perhaps add some middleware to ensure that?\n\n* Create an interface in order to type your Action, so that you can logically group your actions together. Of course you can create type safe hierarchies too like we did.\n\n* Your actions don't need a getPayload() just make them Java beans, its simpler and nice. Use [Lombok](https://projectlombok.org/) (or [Immutables](http://immutables.github.io/)) to keep your boilerplate way down.\n\n## Alternatives\n\nThere are some alternative Java libraries to Rejux if to check out:\n\n* [redux-java](https://github.com/glung/redux-java)\n* [Bansa](https://github.com/brianegan/bansa)\n* [Jedux](https://github.com/trikita/jedux)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcommitd%2Frejux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcommitd%2Frejux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcommitd%2Frejux/lists"}