Boomspot
  • Home
Loading...
Boomspot

Daily tech news, software development coverage, Apple reporting, and the gear behind modern music making.

TwitterLinkedIn

Browse

  • Categories
  • Tags
  • Authors

Company

  • About
  • Contact

Legal

  • Privacy Policy
  • Terms of Service
  • Unsubscribe

© 2026 Boomspot. All rights reserved.

Built by Boomspot
Updated hourly

AI Content Disclosure: Articles on Boomspot are researched, written, and edited with the assistance of advanced AI systems. We combine software-assisted research with editorial oversight to deliver useful, accurate, and practical technical and music production content. Learn more about our editorial approach.

  1. Home
  2. Coding
  3. Ports and Adapters Pattern in Java: A Spring Guide
coding6 min read

Ports and Adapters Pattern in Java: A Spring Guide

A hardcoded REST call becomes three costly rewrites. See how a Spring Boot bridge-api and bridge-impl split isolates transport changes from business logic for good.

S

Staff

August 31, 2026

Ports and Adapters Pattern in Java: A Spring Guide

Somewhere around month four of a project, a pricing service starts talking to an upstream provider over plain REST. Three days to ship, a partner deadline already announced, so a developer wires a RestClient call straight into the business-logic method that needs the price, parses the JSON inline, and moves on. It works, so everyone moves to the next fire.

Two months later the upstream team switches to Kafka, and the shape of "when do we have this data" changes underneath every caller that assumed a synchronous return value. Three weeks after that, edge devices in the field can't hold a Kafka connection reliably, so the team moves again to MQTT. Three rewrites of the same core logic in five months, each one touching more of the codebase than the last because more code had quietly grown to depend on the leaked transport assumption.

That scenario is the cleanest argument you'll find for the ports and adapters pattern, and it's worth taking seriously the next time someone proposes hardcoding a data source "just for now." The pattern's job is to keep the question your domain asks, like "give me the latest price for this SKU," completely separate from whatever mechanism answers it. In a Spring Boot project, that separation maps onto a Maven multi-module layout almost exactly: a bridge-api module holding interfaces and domain types, a bridge-impl module holding the REST, Kafka, or MQTT client that satisfies those interfaces, and a business-logic module depending on bridge-api and nothing else.

Defining the Port Before You Pick a Transport

Get the module boundaries wrong and the pattern buys you nothing, so start with the interface, not the client library. The bridge-api module should have almost no dependencies beyond your domain types:

public interface PricingGateway {
    Optional<Price> getLatestPrice(String sku);
}

Notice what's missing: no RestClient, no DTO, no hint that HTTP is involved anywhere. Price is a plain domain record living in bridge-api too. This interface is the port. It answers the domain's question and refuses to know or care how the answer arrives.

The business-logic module depends only on bridge-api, never on bridge-impl:

@Service
public class PricingService {
    private final PricingGateway pricingGateway;

    public PricingService(PricingGateway pricingGateway) {
        this.pricingGateway = pricingGateway;
    }

    public Price currentPrice(String sku) {
        return pricingGateway.getLatestPrice(sku)
            .orElseThrow(() -> new PriceUnavailableException(sku));
    }
}

Enforce that boundary at the Maven level, not just by convention. If business-logic's pom.xml never lists bridge-impl as a dependency, the compiler makes the boundary real. A developer under deadline pressure can't accidentally import a Kafka consumer into service code that has no business knowing Kafka exists, because the module simply won't compile.

Building the REST Adapter, Then Swapping It for Kafka

With the port defined, bridge-impl holds the first, and eventually not the only, implementation: We cover related ground in aws vs terraform vs kubernetes: what to learn first.

@Component
public class RestPricingGateway implements PricingGateway {
    private final RestClient restClient;

    public RestPricingGateway(RestClient restClient) {
        this.restClient = restClient;
    }

    @Override
    public Optional<Price> getLatestPrice(String sku) {
        PriceDto dto = restClient.get()
            .uri("/prices/{sku}", sku)
            .retrieve()
            .body(PriceDto.class);
        return Optional.ofNullable(dto).map(this::toDomain);
    }

    private Price toDomain(PriceDto dto) {
        return new Price(dto.sku(), dto.amount(), dto.currency());
    }
}

That's the whole adapter, DTO, mapping, and transport client sealed inside bridge-impl, invisible to everything else. When the upstream provider announces a move to Kafka, the fix stays local. Add a Kafka dependency to bridge-impl, write a new adapter, and retire the old one:

@Component
public class KafkaPricingGateway implements PricingGateway {
    private final PriceCache cache;

    public KafkaPricingGateway(PriceCache cache) {
        this.cache = cache;
    }

    @KafkaListener(topics = "pricing-updates")
    public void onPriceEvent(PriceEvent event) {
        cache.put(event.sku(), new Price(event.sku(), event.amount(), event.currency()));
    }

    @Override
    public Optional<Price> getLatestPrice(String sku) {
        return cache.get(sku);
    }
}

Business-logic doesn't recompile. Nothing in PricingService changes, because it never knew RestClient existed in the first place. Spring's component scanning wires whichever gateway bean sits on the classpath, and if both implementations need to coexist during a migration, a @Profile or a feature-flagged @Primary bean picks the winner without touching a single call site downstream.

If MQTT comes next, the same move repeats: a third adapter class, still living inside bridge-impl, still invisible to business-logic. Three transport migrations become three implementations of one interface instead of three rewrites of everything that depended on a leaked assumption.

Also read: a closer look at retrofitting a hash chain into an existing audit log

A Checklist for When the Shortcut Is Actually Safe

None of this means refusing every shortcut on principle, and doing that has its own failure mode: you'll blow real deadlines defending abstractions nobody asked for. The judgment call worth making, every time, comes down to a small set of questions.

First, ask whether the shortcut stays entirely inside a single module or leaks its assumptions into a method signature that other modules call. A hardcoded URL inside bridge-impl is an annoyance you fix on the next pass. A domain method that implicitly assumes synchronous, request-shaped data is a liability that compounds every time someone new builds on top of it.

Second, ask how many call sites would need to change if the answer to "how does this data arrive" changes tomorrow. If the honest answer is "just this one adapter class," ship the shortcut. If the honest answer touches service methods, test mocks, and controller logic scattered across the codebase, the shortcut needs the interface first.

Third, ask whether the return type or method signature itself encodes an assumption about timing, ordering, or transport, because that's the detail that survives longest and hurts most when it turns out wrong.

The uncomfortable truth is that this discipline usually gets built after the pain, not before it, once a team has already lived through a rewrite that should have been a fifteen-minute adapter swap. Building the bridge-api and bridge-impl separation up front costs maybe half a day beyond a tight deadline: one interface, one adapter, tests written against the interface instead of an HTTP client.

Skipping it doesn't remove that cost; it just moves it downstream and converts a schedule risk into a production incident. Usually that incident lands on whoever's on call when a price fails to update and nobody can say which of three transport layers is currently responsible for delivering it.

Tags

Software DevelopmentCoding Best PracticesDeveloper ToolsProgramming LanguagesWeb Development

Related Articles

WebAssembly: Unleashing Native Speed in Web Browsers
coding•4 min read

WebAssembly: Unleashing Native Speed in Web Browsers

WebAssembly is transforming web development with near-native performance, enabling more complex and efficient applications.

Sep 6, 2025

Transforming Mobile Devices: AI Chips from Arm for Developers
coding•4 min read

Transforming Mobile Devices: AI Chips from Arm for Developers

Explore how Arm's AI chips are transforming mobile devices and influencing software development. Insights from Geraint North reveal future trends for developers.

Sep 18, 2025

How to Use Claude Code Subagents to Parallelize Development
coding•3 min read

How to Use Claude Code Subagents to Parallelize Development

Learn how to enhance your development workflow using Claude Code Subagents. This guide provides practical examples for parallelizing coding tasks.

Sep 13, 2025

Browse by Category

Technology594Coding145Linux24SEO16Music Production15Apple Rumors11Studio Gear7

Popular Posts

AIR Fabric Vol 2: Andromeda vs Matrix 12 vs CS-80 Review

AIR Fabric Vol 2: Andromeda vs Matrix 12 vs CS-80 Review

6 min read
AI Coding Agent Cost Ledger: Track Expensive Sessions

AI Coding Agent Cost Ledger: Track Expensive Sessions

7 min read
Read This Before You Buy That TV Streaming Stick

Read This Before You Buy That TV Streaming Stick

6 min read
Landing Pages vs Full Web Apps: Dastarkhwan Case Study

Landing Pages vs Full Web Apps: Dastarkhwan Case Study

5 min read
Harley Benton Space Wah & Volume: 3 New Pedals Compared

Harley Benton Space Wah & Volume: 3 New Pedals Compared

6 min read

Recent Posts

What Is Amazon's Soft Reserve Price? Explained

What Is Amazon's Soft Reserve Price? Explained

Sep 1, 2026•6 min
Force Upgrade Ubuntu 24.04 to 26.04 Early? Read This First

Force Upgrade Ubuntu 24.04 to 26.04 Early? Read This First

Aug 31, 2026•4 min
The Correct Order for Bass Effects Pedals, Explained

The Correct Order for Bass Effects Pedals, Explained

Aug 31, 2026•7 min
Ableton Push 2 vs Push 3: A Buyer's Comparison Guide

Ableton Push 2 vs Push 3: A Buyer's Comparison Guide

Aug 31, 2026•6 min
16-Bit vs 24-Bit Kontakt Libraries: Is Quality Lost?

16-Bit vs 24-Bit Kontakt Libraries: Is Quality Lost?

Aug 31, 2026•5 min