Last active
January 11, 2026 17:42
-
-
Save fResult/72a81980e52ae28bfa24020925e4ef53 to your computer and use it in GitHub Desktop.
This is a part of my post, "When Language Ergonomics Fight Your Architecture (And How to Win)" on LinkedIn (https://www.linkedin.com/feed/update/urn:li:activity:7415629554402111488)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import lombok.val; | |
| public class Java14PlusMain { | |
| public static void main(String... args) { | |
| /* | |
| * ⚠️ DEMO NOTE: Exception Handling Strategy | |
| * We catch exceptions here purely for demonstration simplicity. | |
| * | |
| * 🚨 IN PRODUCTION: Treat them differently. | |
| * 1. IneligibleAgeException (Domain Error): | |
| * - This is a Business Rule (Expected Scenario) | |
| * - STRATEGY: Handle it gracefully (e.g., show UI message) | |
| * - BETTER: Treat Error as Data! Prefer returning `Either<Error, Success>` or `Result` | |
| * instead of throwing exceptions for control flow | |
| * | |
| * 2. InvariantViolationException (Developer Error / Bug): | |
| * - This means the code/data is broken (Contract Violation) | |
| * - STRATEGY: Fail Fast | |
| * - DON'T sweep this under the rug (swallowing the exception) | |
| * - This indicates a BUG in the code. Hiding it creates zombie state | |
| * - Let it CRASH loud and clear, so developers can fix it immediately | |
| */ | |
| try { | |
| val anderson = new EligiblePerson("Thomas", "A.", "Anderson", 30); | |
| System.out.println(anderson); | |
| val constantine = anderson.toBuilder().lastName("Constantine").build(); | |
| System.out.println(constantine); | |
| System.out.printf( | |
| "First Name: %s, Middle Name: %s, Last Name: %s, and Age: %d%n", | |
| constantine.firstName(), | |
| constantine.maybeMiddleName().orElse("-"), | |
| constantine.lastName(), | |
| constantine.age()); | |
| val invalidPerson = constantine.toBuilder().age(16).build(); | |
| System.out.println(invalidPerson); | |
| } catch (InvariantViolationException | IneligibleAgeException ex) { | |
| // In a real app, DON'T catch InvariantViolation (Developer Error), it should fail fast! | |
| System.err.println("Error: " + ex.getMessage()); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import lombok.val; | |
| public class Java8Main { | |
| public static void main(String... args) { | |
| /* | |
| * ⚠️ DEMO NOTE: Exception Handling Strategy | |
| * We catch exceptions here purely for demonstration simplicity. | |
| * | |
| * 🚨 IN PRODUCTION: Treat them differently. | |
| * 1. IneligibleAgeException (Domain Error): | |
| * - This is a Business Rule (Expected Scenario) | |
| * - STRATEGY: Handle it gracefully (e.g., show UI message) | |
| * - BETTER: Treat Error as Data! Prefer returning `Either<Error, Success>` or `Result` | |
| * instead of throwing exceptions for control flow | |
| * | |
| * 2. InvariantViolationException (Developer Error / Bug): | |
| * - This means the code/data is broken (Contract Violation) | |
| * - STRATEGY: Fail Fast | |
| * - DON'T sweep this under the rug (swallowing the exception) | |
| * - This indicates a BUG in the code. Hiding it creates zombie state | |
| * - Let it CRASH loud and clear, so developers can fix it immediately | |
| */ | |
| try { | |
| val anderson = new EligibleEmployee("Thomas", "A.", "Anderson", 30); | |
| System.out.println(anderson); | |
| val constantine = anderson.toBuilder().firstName("John").lastName("Constantine").build(); | |
| System.out.println(constantine); | |
| System.out.printf( | |
| "First Name: %s, Middle Name: %s, Last Name: %s, and Age: %d%n", | |
| constantine.firstName(), | |
| constantine.maybeMiddleName().orElse("-"), | |
| constantine.lastName(), | |
| constantine.age()); | |
| val invalidPerson = constantine.toBuilder().age(16).build(); | |
| System.out.println(invalidPerson); | |
| } catch (InvariantViolationException | IneligibleAgeException ex) { | |
| // In a real app, DON'T catch InvariantViolation (Developer Error), it should fail fast! | |
| System.err.println("Error: " + ex.getMessage()); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.util.Objects; | |
| import java.util.Optional; | |
| import lombok.Builder; | |
| import lombok.Value; | |
| import lombok.experimental.Accessors; | |
| import org.jspecify.annotations.NonNull; | |
| import org.jspecify.annotations.Nullable; | |
| @Value // ← Use Lombok's @Value to simulate Java Record | |
| @Accessors(fluent = true) // ← Great choice to mimic Record accessors | |
| public class EligibleEmployee { | |
| String firstName; | |
| /* | |
| * 💡 Advanced Note: | |
| * If your project already uses Vavr, you can use io.vavr.control.Option as a field type | |
| * since it is Serializable and plays well with functional patterns. | |
| * However, standard java.util.Optional should remain a return type only. | |
| */ | |
| @Nullable String middleName; | |
| String lastName; | |
| int age; | |
| /* JSpecify's/Jakarta EE's @Nonnull helps us make nullability explicit */ | |
| /* | |
| *🛡️ DEFENSIVE CODING: | |
| * @Builder is intentionally placed on this Constructor, NOT the Class. | |
| * | |
| * Why? If placed on the Class, adding a new field without updating this constructor | |
| * would cause Lombok to silently generate a default constructor, BYPASSING these validations. | |
| * | |
| * Keeping it here forces a COMPILE ERROR if fields mismatch, guaranteeing safety. | |
| */ | |
| @Builder(toBuilder = true) | |
| public EligibleEmployee( | |
| @NonNull String firstName, @Nullable String middleName, @NonNull String lastName, int age) { | |
| Objects.requireNonNull(firstName); | |
| Objects.requireNonNull(lastName); | |
| if (age < 25) { | |
| throw new IneligibleAgeException( | |
| "Eligible Employee must be 18+ years old, but got %s".formatted(age)); | |
| } | |
| if (firstName.trim().isEmpty()) { | |
| throw new InvariantViolationException("First Name cannot be empty"); | |
| } | |
| if (middleName != null && middleName.trim().isEmpty()) { | |
| throw new InvariantViolationException("Middle Name cannot be empty"); | |
| } | |
| if (lastName.trim().isEmpty()) { | |
| throw new InvariantViolationException("Last Name cannot be empty"); | |
| } | |
| this.firstName = firstName; | |
| this.middleName = middleName; | |
| this.lastName = lastName; | |
| this.age = age; | |
| } | |
| public EligibleEmployee(@NonNull String firstName, @NonNull String lastName, int age) { | |
| this(firstName, null, lastName, age); | |
| } | |
| /** | |
| * This is our ARCHITECTURE ENFORCEMENT. | |
| * It explicitly disables the static builder entry point to prevent Anemic Object creation. | |
| * This forces clients to use the Constructor (or Factory), ensuring all Invariants are validated. | |
| */ | |
| @SuppressWarnings("unused") | |
| private static EligibleEmployeeBuilder builder() { | |
| throw new UnreachableException( | |
| "The %s#builder is unreachable!".formatted(EligibleEmployee.class.getSimpleName())); | |
| } | |
| public Optional<String> maybeMiddleName() { | |
| return Optional.ofNullable(middleName); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.util.Objects; | |
| import java.util.Optional; | |
| import lombok.Builder; | |
| import org.jspecify.annotations.NullMarked; | |
| import org.jspecify.annotations.Nullable; | |
| // Use JSpecify's @NullMarked to enforce @Nonnull as default for every field | |
| @NullMarked | |
| @Builder(toBuilder = true) | |
| public record EligiblePerson( | |
| String firstName, | |
| /* | |
| * 💡 Advanced Note: | |
| * If your project already uses Vavr, you can use io.vavr.control.Option as a field type | |
| * since it is Serializable and plays well with functional patterns. | |
| * However, standard java.util.Optional should remain a return type only. | |
| */ | |
| @Nullable String middleName, | |
| String lastName, | |
| int age) { | |
| // Compact Constructor: The pride of Java 14+ | |
| public EligiblePerson { | |
| Objects.requireNonNull(firstName); | |
| Objects.requireNonNull(lastName); | |
| final int eligibleAge = 25; | |
| if (age < eligibleAge) { | |
| throw new IneligibleAgeException( | |
| "Eligible Person must be %d+ years old, but got %s".formatted(eligibleAge, age)); | |
| } | |
| if (firstName.trim().isEmpty()) { | |
| throw new InvariantViolationException("First Name cannot be empty"); | |
| } | |
| if (middleName != null && middleName.trim().isEmpty()) { | |
| throw new InvariantViolationException("Middle Name cannot be empty"); | |
| } | |
| if (lastName.trim().isEmpty()) { | |
| throw new InvariantViolationException("Last Name cannot be empty"); | |
| } | |
| } | |
| public EligiblePerson(String firstName, String lastName, int age) { | |
| this(firstName, null, lastName, age); | |
| } | |
| /** | |
| * This is our ARCHITECTURE ENFORCEMENT. | |
| * It explicitly disables the static builder entry point to prevent Anemic Object creation. | |
| * This forces clients to use the Constructor (or Factory), ensuring all Invariants are validated. | |
| */ | |
| @SuppressWarnings("unused") | |
| private static EligiblePersonBuilder builder() { | |
| throw new UnreachableException( | |
| "The %s#builder is unreachable!".formatted(EligiblePerson.class.getSimpleName())); | |
| } | |
| public Optional<String> maybeMiddleName() { | |
| return Optional.ofNullable(middleName); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Assume these Classes/Interfaces are in standalone file for each class/interface | |
| // DeveloperError.java ← to denoted that it is a Developer Error, not Business Error | |
| public sealed interface DeveloperError permits | |
| InvariantViolationException, | |
| UnreachableException {} | |
| // DomainError.java ← to denoted that it is Business Error | |
| public sealed interface DomainError permits IneligibleAgeException {} | |
| // InvariantViolationException.java | |
| public final class InvariantViolationException | |
| extends RuntimeException | |
| implements DeveloperError { | |
| public InvariantViolationException() { | |
| super("Invariant Violation"); | |
| } | |
| public InvariantViolationException(String message) { | |
| super(message); | |
| } | |
| } | |
| // UnreachableException.java | |
| public final class UnreachableException | |
| extends RuntimeException | |
| implements DeveloperError { | |
| public UnreachableException() { | |
| super("This code should be unreachable"); | |
| } | |
| public UnreachableException(String message) { | |
| super(message); | |
| } | |
| } | |
| // IneligibleAgeException.java | |
| public final class IneligibleAgeException | |
| extends RuntimeException | |
| implements DomainError { | |
| public IneligibleAgeException() { | |
| super("Ineiligible Age"); | |
| } | |
| public IneligibleAgeException(String message) { | |
| super(message); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment