{"id":20003056,"url":"https://github.com/37bytes/mgutils","last_synced_at":"2026-02-03T23:04:07.833Z","repository":{"id":176351824,"uuid":"656783377","full_name":"37bytes/mgutils","owner":"37bytes","description":"Common Java Utils","archived":false,"fork":false,"pushed_at":"2024-05-29T14:26:05.000Z","size":193,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-03T02:42:45.495Z","etag":null,"topics":["central","commons","java","java-8","java-library","logging","maven","multithreading","utility"],"latest_commit_sha":null,"homepage":"https://central.sonatype.com/artifact/dev.b37.libs/mgutils","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/37bytes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2023-06-21T16:19:19.000Z","updated_at":"2024-05-29T14:24:23.000Z","dependencies_parsed_at":"2024-11-13T09:15:15.182Z","dependency_job_id":null,"html_url":"https://github.com/37bytes/mgutils","commit_stats":null,"previous_names":["mrgrd56/mgutils"],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/37bytes/mgutils","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/37bytes%2Fmgutils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/37bytes%2Fmgutils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/37bytes%2Fmgutils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/37bytes%2Fmgutils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/37bytes","download_url":"https://codeload.github.com/37bytes/mgutils/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/37bytes%2Fmgutils/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29060644,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T22:28:58.191Z","status":"ssl_error","status_checked_at":"2026-02-03T22:28:56.515Z","response_time":96,"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":["central","commons","java","java-8","java-library","logging","maven","multithreading","utility"],"created_at":"2024-11-13T05:24:08.870Z","updated_at":"2026-02-03T23:04:07.815Z","avatar_url":"https://github.com/37bytes.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mgutils - Common Java Utils\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.b37.libs/mgutils/badge.svg)](https://central.sonatype.com/artifact/dev.b37.libs/mgutils)\n\n\u003e [!Note]  \n\u003e The version displayed here may be outdated, click on the badge to view the latest version  \n\u003e Try the highest version seen\n\n## Description\n\n`mgutils` is a Java library that provides a collection of utilities designed to simplify and enhance your Java development experience. The utilities provided range from data manipulation, multithreading, logging, and more. This library is designed to be lightweight, efficient, and easy to integrate into any Java project.\n\n## Getting Started\n\n### Prerequisites\n\nThe library requires Java 8 or above.\n\n### Installation\n\n1. Go to the [Maven Central page](https://central.sonatype.com/artifact/dev.b37.libs/mgutils) of this library.\n2. Copy the snippet for your package manager. E.g. for Maven you can copy code looking like this:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003edev.b37.libs\u003c/groupId\u003e\n    \u003cartifactId\u003emgutils\u003c/artifactId\u003e\n    \u003cversion\u003eCURRENT_VERSION_HERE\u003c/version\u003e\n\u003c/dependency\u003e\n```\n3. Paste this fragment into your dependency list. In case of Maven, it's the `\u003cdependencies\u003e` section in `pom.xml`.\n\n## Publishing New Version\n\n### Using GitHub Actions _(recommended)_\n\n1. [Create](https://github.com/37bytes/mgutils/releases/new) a new [release](https://github.com/37bytes/mgutils/releases) on this repository. The new version will be published automatically.\n2. You can check out the [`Actions`](https://github.com/37bytes/mgutils/actions) tab to ensure.\n\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;or\n\n1. Go to the [`Actions`](https://github.com/37bytes/mgutils/actions) tab on this repository.\n2. Choose the [`Maven package`](https://github.com/37bytes/mgutils/actions/workflows/maven-publish.yml) action.\n3. Press `Run workflow`.\n4. Check out `All workflows` to see the deployment process.\n\n### Manually\n\nFollow this guide: https://central.sonatype.org/publish/publish-maven\n\n## Overview\n\nLet's see the most useful classes in this library.\n\n- [ScopedLogger](#scopedlogger)\n- [TaskInvoker](#taskinvoker)\n- [MapBuilder](#mapbuilder)\n- [CachedInvoker](#cachedinvoker)\n\n### ScopedLogger\n\n[_dev.b37.mgutils.logging.ScopedLogger_](https://github.com/37bytes/mgutils/blob/master/src/main/java/dev/b37/mgutils/logging/ScopedLogger.java)\n\n**Tired of digging through messy logs to make sense of your application’s behavior? ScopedLogger is here to neatly organize your logs and give them the clarity they need!** _(с) ChatGPT_\n\n`ScopedLogger` is a class that augments traditional logging by adding scope to logging operations. This functionality helps group related log messages together by attaching a `scope name` and a unique `scope ID` to each log message. This is particularly useful when tracking the flow of control in the logs, especially in cases where there are nested scopes.\n\nIt's important to note that `ScopedLogger` is a decorator class on the [slf4j](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) `Logger` interface. This means that `ScopedLogger` is always created using a \"basic\"/\"outer\" logger and just changes its behavior. Any `Logger` can be used as a base, even another `ScopedLogger` (which lets you create nested `ScopedLogger`s).\n\n```java\npublic class Example {\n    private final Logger log = LoggerFactory.getLogger(this.getClass());\n    \n    public Post fetchPost(int postId) {\n        Logger logger = ScopedLogger.of(log, \"fetchPost:\"); // 1. Creating a ScopedLogger\n        logger.trace(\"fetching postId={}\", postId); // 2. Using it like any other Logger!\n        \n        Response response = http.request(\"http://localhost:8080/posts/\" + postId);\n        logger.trace(\"got the response {}\", response);\n        \n        Post post = response.getBody();\n        logger.trace(\"got the post {}\", post);\n        for (Comment comment : post.getComments()) {\n            Logger commentLogger = ScopedLogger.of(logger, \"fetchComment:\", comment.getId()); // 3. Create nested `ScopedLogger`s if you wish\n            commentLogger.trace(\"fetching commentId={}\", comment.getId());\n            \n            Response commentResponse = http.request(\"http://localhost:8080/posts/\" + postId);\n            commentLogger.trace(\"got the response {}\", response);\n            \n            commentsHelper.populateComment(comment, commentResponse.getBody());\n            commentLogger.trace(\"populated comment\");\n        }\n        logger.trace(\"successfully fetched the post\");\n        return post;\n    }\n}\n```\n\nCalling `Example#fetchPost` will output something like:\n\n```\nTRACE [48y9zeqq2c2d] fetchPost: fetching postId=2690201\nTRACE [48y9zeqq2c2d] fetchPost: got the response Response@3769fecf\nTRACE [48y9zeqq2c2d] fetchPost: got the post Post@5d244b79\nTRACE [48y9zeqq2c2d] fetchPost: [52] fetchComment: fetching commentId=52\nTRACE [48y9zeqq2c2d] fetchPost: [52] fetchComment: got the response Response@14cb4e66\nTRACE [48y9zeqq2c2d] fetchPost: [52] fetchComment: populated comment\nTRACE [48y9zeqq2c2d] fetchPost: [63] fetchComment: fetching commentId=63\nTRACE [48y9zeqq2c2d] fetchPost: [63] fetchComment: got the response Response@4a7b18fd\nTRACE [48y9zeqq2c2d] fetchPost: [63] fetchComment: populated comment\nTRACE [48y9zeqq2c2d] fetchPost: successfully fetched the post\n```\n\nSecond calling `Example#fetchPost` will output logs with different random `scopeId`:\n\n```\nTRACE [juo0n1nal8m5] fetchPost: fetching postId=2690201\nTRACE [juo0n1nal8m5] fetchPost: got the response Response@3769fecf\n...\nTRACE [juo0n1nal8m5] fetchPost: [63] fetchComment: populated comment\nTRACE [juo0n1nal8m5] fetchPost: successfully fetched the post\n```\n\nThe identifiers used by `ScopedLogger` are called `scopeName` and `scopeId`. In the case above, in `logger`, the `scopeName` is `\"fetchPost:\"` and the `scopeName` is `\"48y9zeqq2c2d\"` (randomly generated).\n\nThe `scopeName` typically represents a method or block of code, while the `scopeId` is a unique identifier created for each new instance of a `ScopedLogger` at the time of a new invocation of a block of code represented by the `scope name`. When a `ScopedLogger` is created from another `ScopedLogger`, all the scope names and IDs are included in the log messages, which assists in tracking nested and interdependent log entries​.\n\nNote that `scopeId` can be set manually, like in the comments loop in the code above. Also, `null` value can be passed to the `scopeId` parameter. In this case, `scopeId` won't be shown. If you don't pass the `scopeId`, it's randomly generated using `ScopedLogger.createScopeId()` by default.\n\nIt's possible to create `ScopedLogger`s another way as well. You can use `ScopedLoggerFactory` to create `ScopedLogger`s with the same source logger:\n\n```java\npublic class Example {\n    private final ScopedLogger logs = new ScopedLogger(LoggerFactory.getLogger(this.getClass()));\n    \n    public Post fetchPost(int postId) {\n        Logger logger = logs.createLogger(\"fetchPost:\");\n        logger.trace(\"fetching postId={}\", postId);\n        // \u003c...\u003e\n    }\n}\n```\n\nIt works the same way, but you avoid using static methods and specifying the same logger every time.\n\n### TaskInvoker\n\n[_dev.b37.mgutils.concurrent.TaskInvoker_](https://github.com/37bytes/mgutils/blob/master/src/main/java/dev/b37/mgutils/concurrent/TaskInvoker.java)\n\nThe `TaskInvoker` class is designed to execute a specific set of tasks, distributing them among threads using an ExecutorService. Tasks can be submitted for execution, but the execution doesn't start immediately. Instead, all tasks are stored and later executed when the `completeAll()` method is called. This method also waits for all tasks to finish and returns the results. `TaskInvoker` supports the submission of both `Runnable` and `Callable` tasks, with or without return values​.\n\nIt can be considered as an alternative to the `ExecutorService#invokeAll` method without having to create a `Collection` of tasks and results explicitly.\n\nLet's see some examples.\n\nWe'll use this `doStuff` method as one that performs some long task.\n\n```java\nString doStuff(int number) throws Exception {\n    // Here's a task returning some data\n    Thread.sleep(150);\n    return \"Number \" + number;\n}\n```\n\nUsing plain `ExecutorService`:\n\n```java\nExecutorService executor = Executors.newFixedThreadPool(50);\nList\u003cCallable\u003cString\u003e\u003e tasks = new ArrayList\u003c\u003e();\n\nfor (int i = 0; i \u003c 60; i++) {\n    int number = i;\n    tasks.add(() -\u003e doStuff(number));\n}\n\nList\u003cFuture\u003cString\u003e\u003e resultFutures;\ntry {\n    resultFutures = executor.invokeAll(tasks);\n} catch (InterruptedException e) {\n    throw new RuntimeException(e);\n}\n\nList\u003cString\u003e results = resultFutures.stream()\n        .map(stringFuture -\u003e {\n            try {\n                return stringFuture.get();\n            } catch (InterruptedException | ExecutionException e) {\n                throw new RuntimeException(e);\n            }\n        })\n        .collect(Collectors.toList());\n```\n\nUsing `TaskInvoker`:\n\n```java\nExecutorService executor = Executors.newFixedThreadPool(50);\nTaskInvoker\u003cString\u003e invoker = new TaskInvoker\u003c\u003e(executor);\n\nfor (int i = 0; i \u003c 60; i++) {\n    int number = i;\n    invoker.submit(() -\u003e doStuff(number));\n}\n\nList\u003cString\u003e results = invoker.completeAll();\n```\n\nAlso, tasks in `TaskInvoker` can be cancelled using the `cancelAll` method. Here's an example:\n\n```java\nExecutorService executor = Executors.newFixedThreadPool(5);\nTaskInvoker\u003cVoid\u003e invoker = new TaskInvoker\u003c\u003e(executor);\n\nQueue\u003cString\u003e results = new ConcurrentLinkedQueue\u003c\u003e();\nAtomicInteger counter = new AtomicInteger(0); // just to know when to cancel the tasks\n\nfinal int MAX_COUNT = 100;\n\nfor (int i = 0; i \u003c 100; i++) {\n    int number = i;\n    invoker.submit(() -\u003e {\n        if (counter.getAndIncrement() == 6) {\n            invoker.cancelAll(); // cancelling the remaining tasks\n        }\n        results.add(doStuff(number));\n    });\n}\ntry {\n    invoker.completeAll();\n} catch (CancellationException e) {\n    // an exception will be thrown\n}\n\n// the results collection contains less than 100 items\n```\n\nAs soon as `completeAll()` is called, the remaining tasks are immediately marked as cancelled and an attempt to execute them by `TaskInvoker` will lead to `CancellationException`.\n\nSince `completeAll()` throws an exception when the tasks are cancelled, we have to collect the results manually, if needed. Note that it's also possible to pass a function without a returning value to `invoker.submit(...)`, that is not possible with `executor.invokeAll` (`Callable\u003cVoid\u003e` still requires returning `null`).\n\n### MapBuilder\n\n[_dev.b37.mgutils.collections.MapBuilder_](https://github.com/37bytes/mgutils/blob/master/src/main/java/dev/b37/mgutils/collections/MapBuilder.java)\n\nCreated as an alternative for Java `Map.ofEntries` which is not available in Java 8. But unlike `Map.ofEntries`, `MapBuilder` is designed for creating _mutable_ `Map`s as well as populating existing ones.\n\nThere is also an alternative to Java `Map.entry` - `MapBuilder.entry`, which is also unavailable in Java 8. It creates an instance of `Map.Entry` using a custom implementation. Unlike `Map.entry`, `MapBuilder.entry` allows using `null` keys and values.\n\nHere's an example:\n\n```java\n    public void testMapBuilder() {\n        // creating new map\n        ConcurrentHashMap\u003cString, Object\u003e response = MapBuilder.create(ConcurrentHashMap::new, // specifying custom map implementation\n                MapBuilder.entry(\"code\", 200),\n                MapBuilder.entry(\"status\", \"OK\"),\n                MapBuilder.entry(\"data\", MapBuilder.create( // using the default (HashMap) implementation\n                        MapBuilder.entry(\"person\", MapBuilder.create(LinkedHashMap::new, // specifying custom map implementation\n                                MapBuilder.entry(\"id\", 42125124),\n                                MapBuilder.entry(\"name\", \"John\")\n                        ))\n                ))\n        );\n\n        // populating existing map, returns the same map\n        // `response` is modified\n        // `sameMap` and `response` refer to the same object\n        ConcurrentMap\u003cString, Object\u003e sameMap = MapBuilder.populate(response,\n                Map.entry(\"version\", \"1.4.2\"),\n                Map.entry(\"hasData\", response.get(\"data\") != null));\n\n        // using alternative syntax\n        Map\u003cString, Object\u003e response2 = new MapBuilder\u003cString, Object\u003e(ConcurrentHashMap::new)\n                .put(\"code\", 200)\n                .put(\"data\", new MapBuilder\u003c\u003e()\n                        .put(\"personId\", 42125124)\n                        .build())\n                .build();\n    }\n```\n\n### CachedInvoker\n\n[_dev.b37.mgutils.concurrent.execution.cached.CachedInvoker_](https://github.com/37bytes/mgutils/blob/master/src/main/java/dev/b37/mgutils/concurrent/execution/cached/CachedInvoker.java)\n\nno description yet\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F37bytes%2Fmgutils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F37bytes%2Fmgutils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F37bytes%2Fmgutils/lists"}