{"id":28730014,"url":"https://github.com/suppierk/pico-types","last_synced_at":"2026-05-30T20:31:47.469Z","repository":{"id":297521948,"uuid":"997070399","full_name":"SuppieRK/pico-types","owner":"SuppieRK","description":"Type wrappers to maintain domain context","archived":false,"fork":false,"pushed_at":"2025-06-05T23:30:20.000Z","size":0,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-06-05T23:30:41.654Z","etag":null,"topics":["java-17","library","wrapper-library","wrappers"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SuppieRK.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":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-05T22:59:07.000Z","updated_at":"2025-06-05T23:30:21.000Z","dependencies_parsed_at":"2025-06-05T23:40:56.261Z","dependency_job_id":null,"html_url":"https://github.com/SuppieRK/pico-types","commit_stats":null,"previous_names":["suppierk/pico-types"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/SuppieRK/pico-types","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SuppieRK%2Fpico-types","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SuppieRK%2Fpico-types/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SuppieRK%2Fpico-types/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SuppieRK%2Fpico-types/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SuppieRK","download_url":"https://codeload.github.com/SuppieRK/pico-types/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SuppieRK%2Fpico-types/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260021690,"owners_count":22947009,"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","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-17","library","wrapper-library","wrappers"],"created_at":"2025-06-15T17:30:26.110Z","updated_at":"2026-05-30T20:31:47.463Z","avatar_url":"https://github.com/SuppieRK.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pico Types\n\n\u003e The work on this software project is in no way associated with my employer nor with the role I'm having at my\n\u003e employer.\n\u003e\n\u003e I maintain this project alone and as much or as little as my **spare time** permits using my **personal** equipment.\n\nThin abstract wrappers over Java data types to reinforce your domain with compile-type checks.\n\n## [How to add?](https://central.sonatype.com/artifact/io.github.suppierk/pico-types)\n\n- **Maven**\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003eio.github.suppierk\u003c/groupId\u003e\n  \u003cartifactId\u003epico-types\u003c/artifactId\u003e\n  \u003cversion\u003e2.0.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n- **Gradle** (_works for both Groovy and Kotlin_)\n\n```groovy\nimplementation(\"io.github.suppierk:pico-types:2.0.1\")\n```\n\n## Technical aspects\n\n- Built for Java 17.\n- JSpecify as the only third-party dependency.\n- 100% unit and mutation test coverage.\n- `equals` and `hashCode` out of the box, no need to use Lombok - tested by [EqualsVerifier](https://github.com/jqno/equalsverifier).\n- Extensibility - all classes are `abstract` and ready to be implemented:\n    - In case when you need your own more specific type - you can simply extend `PicoType` itself.\n- `Optional`-like API\n\n## Build \u0026 Quality\n\n[![Build status](https://github.com/SuppieRK/pico-types/actions/workflows/build.yml/badge.svg)](https://github.com/SuppieRK/pico-types/actions/workflows/build.yml)\n[![Maven Central](https://img.shields.io/maven-central/v/io.github.suppierk/pico-types.svg)](https://search.maven.org/artifact/io.github.suppierk/pico-types)\n[![Javadoc](https://javadoc.io/badge2/io.github.suppierk/pico-types/javadoc.svg)](https://javadoc.io/doc/io.github.suppierk/pico-types)\n[![SonarCloud Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=SuppieRK_pico-types\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=SuppieRK_pico-types)\n[![SonarCloud Coverage](https://sonarcloud.io/api/project_badges/measure?project=SuppieRK_pico-types\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=SuppieRK_pico-types)\n[![SonarCloud Maintainability](https://sonarcloud.io/api/project_badges/measure?project=SuppieRK_pico-types\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=SuppieRK_pico-types)\n\nThe CI workflow uploads the latest JaCoCo (≈105 KB) and PIT mutation (≈44 KB) reports as build artifacts for every run.\n\n## Compatibility\n\n| Area          | Support                                                         |\n|---------------|-----------------------------------------------------------------|\n| Minimum JDK   | 17 (enforced via Gradle toolchains)                             |\n| CI JDKs       | Temurin 17 on Ubuntu runners                                    |\n| Build tooling | Gradle 8.14 wrapper (consumers can use any Maven/Gradle client) |\n\n## What is the problem?\n\nYou have probably seen constructs in the code similar to:\n\n```java\n// Somewhere across repositories\nUUID place(Collection\u003cUUID\u003e entityIds);\n\n// Somewhere in the API\nUUID fetchUser(UUID companyId);\n\n// Also can happen within the same API\nUUID placeOrder(UUID userId, UUID merchantId);\n```\n\nThere is nothing conceptually wrong with these examples, and we have smart IDEs to help us fetch the context - but we\ncan do this:\n\n```java\nUUID userId;\nUUID merchantId;\n\n// There is nothing wrong with this language wise - except when it will reach production.\nuserId = placeOrder(merchantId, userId);\n```\n\nWouldn't it be nicer if previous examples could be rewritten with more support from the language itself?\n\n```java\n// Oh, we are actually placing items in the cart!\nCartId place(Collection\u003cItemId\u003e ids);\n\n// We were in fact looking for an employee in an affiliate company!\nEmployeeId fetchUser(AffiliateCompanyId companyId);\n\n// We won't be able to make the same mistake - it will simply not compile\nOrderId placeOrder(UserId userId, MerchantId merchantId);\n```\n\n## How the solution is achieved with this library?\n\nLet's take a look at the working example of the previous problem with `placeOrder`:\n\n```java\nimport java.util.UUID;\n\npublic class Solution {\n    public static class UserId extends UuidPicoType {\n        public UserId(UUID value) {\n            super(value);\n        }\n    }\n\n    public static class MerchantId extends UuidPicoType {\n        public MerchantId(UUID value) {\n            super(value);\n        }\n    }\n\n    public static class OrderId extends UuidPicoType {\n        public OrderId(UUID value) {\n            super(value);\n        }\n    }\n\n    public static OrderId placeOrder(UserId userId, MerchantId merchantId) {\n        return new OrderId(UUID.randomUUID());\n    }\n\n    public static void main(String[] args) {\n        var userId = new UserId(UUID.randomUUID());\n        var merchantId = new MerchantId(UUID.randomUUID());\n\n        // Now you will get a compile time warning if you will try anything from the problem above\n        var orderId = placeOrder(userId, merchantId);\n        System.out.println(orderId);\n    }\n}\n```\n\n## PicoType API at a glance\n\n| Method                                                             | Summary                                                                         |\n|--------------------------------------------------------------------|---------------------------------------------------------------------------------|\n| `T value()`                                                        | Returns the wrapped value (nullable).                                           |\n| `boolean isPresent()` / `isEmpty()`                                | Presence checks mirroring `Optional`.                                           |\n| `void ifPresent(Consumer)` / `ifPresentOrElse(Consumer, Runnable)` | Execute callbacks depending on presence.                                        |\n| `PicoType\u003cT\u003e or(Supplier)`                                         | Lazily replace an empty instance.                                               |\n| `Stream\u003cT\u003e stream()`                                               | Expose the content as a single-value stream.                                    |\n| `T orElse(T)` / `orElseGet(Supplier)`                              | Defaulting helpers identical to `Optional`.                                     |\n| `T orElseThrow()` / `orElseThrow(Supplier)`                        | Fail fast when no value is present.                                             |\n| `equals` / `hashCode` / `toString`                                 | Final overrides supplied by concrete base classes to guarantee value semantics. |\n\n\u003e Optional parity in terms of `map`/`flatMap` was skipped because these wrappers are typed and expected to work with\n\u003e only one value type.\n\n## What are the benefits?\n\n### Improved productivity\n\nWe hold on to a lot of context in our heads - tickets to fix, functionality to implement, system design, etc.\n\nBeing able to just read the code and understand what needs to be done is a relief.\n\n### Yet another control to maintain the architecture and improve cross-team communication\n\nThe main point of the Domain-Driven Design. When there is no confusion around `com.shiny.CustomerUserId` - there is no \"\n_Hold on, when we say customer ID - what do we mean by customer?_\" type of questions on meetings - less time wasted on\nexplanations for seemingly obvious things to some and completely unknown to others.\n\n## What are the caveats of this approach?\n\n\u003e **To a man with a hammer, everything looks like a nail.**\n\u003e\n\u003e _Abraham Maslow, The Psychology of Science, 1966_\n\n### Confusion\n\n\u003e Is `UserId` returned from one service the same as `UserId` consumed (or returned) by another service?\n\nThe only reliable solution to this problem is adopting Domain-Driven Design and building\na [ubiquitous language dictionary](https://ddd-practitioners.com/home/glossary/ubiquitous-language/).\n\nFor example, if we distinguish customers and merchants - it makes sense to stick to the `CustomerUserId` and\n`MerchantUserId` (duh!).\n\n### More confusion, this time from Java\n\n\u003e Two services from different teams, where one has `com.shiny.team1.CustomerUserId` and another has\n`com.shiny.team2.CustomerUserId` - are these equal?\n\nYou probably won't be surprised by the answer - it\nis [ubiquitous language dictionary](https://ddd-practitioners.com/home/glossary/ubiquitous-language/) again, this time\nin a different flavor:\n\nDepending on the architecture of your system, if all services are consumers of the same API it will make sense to\nenforce using specific object from that service, e.g. if everyone connect to `team1` team service, there must be only\n`com.shiny.team1.CustomerUserId`.\n\n- However, once this system constraint will be broken (and it will be) there will be a lot of migration pain.\n\nIt would be best to introduce a company-wide library with `com.shiny.CustomerUserId`.\n\nIt might be tempting to re-wrap IDs to avoid having this library - this will only hide the symptoms and will not solve\nthe problem.\n\n### Verbosity\n\n\u003e Also known as `ThisTimeThisIsForSureCustomerUserIdPinkySwearAndPromise`.\n\nPlease, don't do that. ChatGPT can be quite helpful if you are stuck with the naming.\n\n### Excessiveness\n\nConsider this example:\n\n```java\nUUID doingSuperImportantWork(\n        UUID userId,\n        UUID companyId,\n        Instant startingFrom,\n        int employeeCount,\n        double fare,\n        boolean includeWeekend\n);\n```\n\nIt might be tempting to do something like:\n\n```java\nSuperImportantWorkId doingSuperImportantWork(\n        UserId userId,\n        CompanyId companyId,\n        StartTime startingFrom,\n        EmployeeCounter employeeCount,\n        Fare fare,\n        WeekendToggle includeWeekend\n);\n```\n\nbut it is just too verbose - so use this in moderation, like so:\n\n```java\nSuperImportantWorkId doingSuperImportantWork(\n        UserId userId,\n        CompanyId companyId,\n        Instant startingFrom,\n        int employeeCount,\n        Fare fare,\n        boolean includeWeekend\n);\n```\n\nor (better yet) avoid having this many parameters for a method and split it onto smaller methods, otherwise if not\npossible introduce a single object capturing these parameters within.\n\n## Implementation notes\n\n### Why there are no types for `Instant`, etc.?\n\nI rarely saw confusion cases related to dates - we usually tend to either work with `createdDate` and `updatedDate` (\nsometimes can be more). If you need to cover this (or any other) specific use case - you can easily create your own\n`PicoType`.\n\n### Nullability\n\nIn order to make sure these types play nicely with databases, they should support `null`.\n\nWith that being said - it is often quite handy to have `Optional`-like API around, if you prefer to maintain null-safety\nacross your codebase and do not have to write constructs like `Optional\u003cMyPicoType\u003e` which can quickly turn into a\nwrapper fest.\n\n### Extending generic signature\n\nSometimes we need to return more than one value - this is where people turn their attention to classes like `Pair` from\nApache Commons.\n\nWhile being handy, I suggest to avoid this approach and use proper POJO objects / records.\n\nAnother reason why you want to avoid having more than one generic is `Optional`-like API - while it could work under\nassumption that both values of both types must not be `null`, we might need to have a combination where one value is\nnullable and the other is not: in that case, as I described before, please, consider using POJO / records.\n\n## Interoperability\n\n### Jackson (GSON, etc.)\n\nSince you will have to `extend` these classes, you can easily add support for your serialization library - here is an\nexample for `UUID` and Jackson:\n\n```java\npublic class MyUuidType extends UuidPicoType {\n    public MyUuidType(UUID value) {\n        super(value);\n    }\n\n    @JsonCreator\n    public MyUuidType(String value) {\n        this(UUID.fromString(value));\n    }\n\n    @Override\n    @JsonValue\n    public UUID value() {\n        return super.value();\n    }\n}\n```\n\n\u003e It is a good idea to make this class `final` and its constructor `private` to disrupt possible inheritance chain.\n\n\u003e This example is taken from `UuidPicoTypeTest` in this repository.\n\n## Versioning\n\n- Semantic Versioning: patch releases keep the same API, minor releases may add new types or defaults while maintaining\n  binary compatibility, and major releases may introduce breaking changes.\n- Java cadence: the library targets Java 17 and evaluates newer LTS releases as they become available; older JDKs are\n  not supported.\n\n## Maintenance\n\nMaintained in spare time by a single contributor. Issues and pull requests are welcome, but triage and response times\nmay vary depending on availability.\n\n## Inspired by\n\n- [Tiny Types](https://github.com/caligin/tinytypes)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuppierk%2Fpico-types","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuppierk%2Fpico-types","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuppierk%2Fpico-types/lists"}