{"id":51045356,"url":"https://github.com/pedro-git-projects/immutable","last_synced_at":"2026-06-22T13:30:31.570Z","repository":{"id":362249294,"uuid":"1258062481","full_name":"pedro-git-projects/immutable","owner":"pedro-git-projects","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-03T08:51:06.000Z","size":8,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-03T10:22:47.408Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"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/pedro-git-projects.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-03T08:41:39.000Z","updated_at":"2026-06-03T08:51:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pedro-git-projects/immutable","commit_stats":null,"previous_names":["pedro-git-projects/immutable"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/pedro-git-projects/immutable","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedro-git-projects%2Fimmutable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedro-git-projects%2Fimmutable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedro-git-projects%2Fimmutable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedro-git-projects%2Fimmutable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pedro-git-projects","download_url":"https://codeload.github.com/pedro-git-projects/immutable/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedro-git-projects%2Fimmutable/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34651747,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"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-06-22T13:30:30.294Z","updated_at":"2026-06-22T13:30:31.561Z","avatar_url":"https://github.com/pedro-git-projects.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Immutable DTOs with Java Records\n\n### The Problem We Have Today\n\nWe duplicate DTO classes across modules. When one copy is updated, others fall behind — and that causes bugs.\n\nBut duplication isn't our only problem. Our DTOs are **mutable**, and that hurts us in two concrete ways **right now**:\n\n---\n\n### 1. Mutation After Construction\n\n```java\nvar order = new OrderDTO();\norder.setId(id);\n// ... 30 lines later, in a different service ...\norder.setTotal(null); // \"temporarily\" — forgot to set it back\n```\n\nAny method that receives a DTO can't trust its state. You get temporal coupling: code that only works if methods happen to run in the right order. Every `setX` call is a place where a future bug can hide.\n\n---\n\n### 2. Leaked Mutable References\n\n```java\npublic List\u003cItemDTO\u003e getItems() {\n    return items; // caller can .add(), .clear(), .remove()...\n}\n```\n\nAnyone calling `getItems()` holds a live reference to the DTO's internals. One accidental `.add()` and the DTO's state is corrupted — silently, without any setter being called.\n\n---\n\n### The Fix: Java Records\n\nJava 21 gives us `record` — immutable by design, with less code than what we have now.\n\n```java\n// BEFORE: ~40 lines\npublic class OrderDTO {\n    private String id;\n    private List\u003cItemDTO\u003e items;\n    private BigDecimal total;\n\n    public OrderDTO() {}\n\n    public String getId() { return id; }\n    public void setId(String id) { this.id = id; }\n    public List\u003cItemDTO\u003e getItems() { return items; } // 💣 leaked ref\n    public void setItems(List\u003cItemDTO\u003e items) { this.items = items; }\n    public BigDecimal getTotal() { return total; }\n    public void setTotal(BigDecimal total) { this.total = total; }\n\n    // equals, hashCode, toString...\n}\n\n// AFTER: 6 lines\npublic record OrderDTO(\n    String id,\n    List\u003cItemDTO\u003e items,\n    BigDecimal total\n) {\n    public OrderDTO {\n        items = List.copyOf(items); // safe forever\n    }\n}\n```\n\n**Less code. No setters. No leaked references. Free `equals`/`hashCode`/`toString`.**\n\n---\n\n### \"But we don't have threading issues\"\n\nCorrect — and this isn't about threads. It's about **local reasoning**.\n\nWhen a DTO is mutable, every method that touches it is a suspect. You read line 40 and ask: *\"could the DTO from line 12 have changed by now?\"*\n\nWith an immutable DTO, the answer is always **no**. The value you received is the value you have.\n\nThis reduces the cognitive load on every developer, every day, in every review.\n\n\u003e (And when someone eventually adds `@Async`, virtual threads, or reactive endpoints — we're already safe.)\n\n---\n\n### FAQ\n\n| Concern | Answer |\n|---|---|\n| **Spring/Jackson can't deserialize records** | Works since Spring Boot 2.7+ / Jackson 2.12+. We're fine. |\n| **We need to build DTOs incrementally** | Use a Builder (Lombok `@Builder` works on records). Immutable once built. |\n| **Big refactor, no user-facing change** | Every duplicated DTO is a place where someone updates one copy and forgets the other. That *is* user-facing when it ships a bug. |\n| **Too much work** | The migration is mechanical: delete setters, rename class → record, add `List.copyOf` where needed. |\n\n---\n\n### The Proposal\n\nWe're already planning to deduplicate the DTOs. While we're touching every one of them:\n\n1. **Consolidate** into a shared module — one source of truth.\n2. **Convert** to `record` — less code, not more.\n3. **Defensive-copy** collection fields in compact constructors.\n\nWe end up with fewer files, fewer lines, fewer bugs, and DTOs that are trivially safe to pass anywhere.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpedro-git-projects%2Fimmutable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpedro-git-projects%2Fimmutable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpedro-git-projects%2Fimmutable/lists"}