Apache Kafka vs RabbitMQ for Messaging in Java (and Where ActiveMQ Fits In)

If you’re standing in front of a whiteboard in Java land and someone has just drawn a box labelled “message queue,” you’re probably going to argue about Apache Kafka and RabbitMQ for the next forty minutes. They’ve become the default two-horse race for back-end messaging, and the choice between them isn’t really about speed or popularity — it’s about whether you want a queue or a log. Get that distinction right and the rest of the decision falls out cleanly. ☕

The One-Sentence Difference

RabbitMQ is a traditional message broker: producers push messages into queues, consumers pop them off, and once a message is acknowledged, it’s gone. Apache Kafka is a distributed commit log: producers append records to partitioned topics, consumers read at their own pace by tracking offsets, and records hang around for a configured retention period — usually days — whether anyone has read them or not. 💡

Everything else flows from that. The protocols differ, the durability model differs, the scaling model differs, the operational pain points differ. But the shape of the abstraction is the thing.

RabbitMQ in 90 Seconds

RabbitMQ implements the Advanced Message Queuing Protocol (AMQP) 0-9-1 by default, with plugins for Message Queuing Telemetry Transport (MQTT), Streaming Text Oriented Messaging Protocol (STOMP), and a Hypertext Transfer Protocol (HTTP) API. It’s written in Erlang, which is part of the secret to its reliability — Erlang’s process model is excellent at the kind of supervised, fault-tolerant work a broker has to do.

The mental model is exchanges, bindings, and queues. A producer publishes to an exchange; the exchange routes the message to zero or more queues based on bindings; consumers subscribe to queues. Routing can be direct (exact key match), topic (pattern match), fanout (broadcast), or headers-based. Once a queue holds a message and a consumer acknowledges it, the broker deletes it. 🐰

A minimal Spring Boot consumer:

1
2
3
4
5
6
7
8
@Component
public class OrderListener {

    @RabbitListener(queues = "orders.created")
    public void onOrderCreated(OrderCreatedEvent event) {
        // ...handle the order
    }
}

You add spring-boot-starter-amqp, declare your queue and exchange as @Beans, and you’re done. The library handles connections, channels, retries, and message conversion via Jackson.

Apache Kafka in 90 Seconds

Kafka is not a broker in the RabbitMQ sense. It’s a partitioned, replicated, append-only log running across a cluster of brokers. Producers write records to topics; each topic is split into partitions; each partition is an ordered, immutable sequence of records stored on disk and replicated to a configurable number of brokers. Consumers read by maintaining their own position (an offset) into each partition.

The model has two consequences that surprise people coming from RabbitMQ:

  • Reading a record doesn’t delete it. The next consumer (or the same one, restarted) can read it again by rewinding the offset. Retention is time-based or size-based.
  • Ordering is per-partition, not per-topic. If you need strict ordering across a logical key (“all events for user 42 in order”), you partition on that key.

A Spring Boot consumer:

1
2
3
4
5
6
7
8
@Component
public class OrderEventListener {

    @KafkaListener(topics = "orders", groupId = "order-processor")
    public void onOrderEvent(ConsumerRecord<String, OrderEvent> record) {
        // ...process the event
    }
}

Same shape, totally different machine underneath.

So… Is ActiveMQ the New Kafka?

Short answer: no. ActiveMQ is the new RabbitMQ — or rather, it has always been RabbitMQ’s older Java-ecosystem sibling. Apache ActiveMQ is a Java Message Service (JMS)-first broker that has been around since 2004. The modern version, ActiveMQ Artemis (formerly HornetQ, donated by Red Hat to the Apache Software Foundation), is a ground-up rewrite that delivers genuinely high throughput and a much better internal architecture than the original ActiveMQ “Classic.”

Artemis is fast, supports AMQP, STOMP, MQTT, OpenWire, and JMS, and is a perfectly reasonable broker. But it’s still a broker. It deletes messages on acknowledgement, it tracks consumer state inside the broker, and it doesn’t have Kafka’s “the log is the truth, rewind whenever” model.

The reason people conflate ActiveMQ and Kafka is that Artemis has gotten fast enough that the old throughput argument doesn’t apply — “use Kafka because ActiveMQ can’t keep up” was true in 2014, not in 2022. But the architectural difference is unchanged: Kafka is a log; ActiveMQ is a queue. Pick based on the abstraction, not the throughput number. 📊

Side-by-Side Comparison

Aspect RabbitMQ Apache Kafka ActiveMQ Artemis
Core abstraction Broker (queue) Distributed log Broker (queue)
Implementation language Erlang Scala / Java Java
Wire protocols AMQP 0-9-1, MQTT, STOMP Custom binary (Kafka protocol) AMQP, MQTT, STOMP, OpenWire, JMS
Message lifetime Deleted on ack Retained for configured period (e.g. 7 days) Deleted on ack
Consumer state Tracked in broker Tracked by consumer (offset) Tracked in broker
Replay Not natively; needs a dead-letter dance First-class — just reset the offset Not natively
Ordering guarantee Per queue Per partition Per queue
Throughput ceiling Tens of thousands msg/s per node Hundreds of thousands to millions msg/s per cluster Tens of thousands msg/s per node
Latency Sub-millisecond achievable Few-millisecond typical (batch-oriented) Sub-millisecond achievable
Routing model Rich (exchanges, bindings, headers) Topic + partition key, no broker-side routing Rich (similar to RabbitMQ)
Transactions Yes (broker) Yes (across topics, idempotent producer) Yes (JMS XA)
Stream processing External (e.g. Spring Cloud Stream) First-class (Kafka Streams, ksqlDB) External
Cluster coordinator Built-in (Erlang clustering / Khepri or Mnesia) ZooKeeper, or KRaft on newer versions Built-in
Management UI Web UI ships in-box None official; tools like AKHQ, Conduktor Web console ships in-box
Spring starter spring-boot-starter-amqp spring-kafka spring-boot-starter-artemis
Best fit Task queues, RPC, complex routing Event streaming, audit logs, change-data-capture JMS-heavy enterprises, AMQP polyglot

How to Actually Choose

Forget the benchmarks. Ask these three questions:

  1. Do I need to replay history? If a new consumer needs to see events from yesterday, last week, or last month, you want Kafka. If “once it’s processed, it’s gone” is your model, you want a broker.
  2. Do I need broker-side routing? If your producer publishes one logical event and seven different consumers each need a different subset based on routing keys or headers, that’s RabbitMQ’s home turf. Kafka does this differently — every consumer reads the whole topic and filters client-side, or you write a separate topic per logical subset.
  3. What does “high throughput” actually mean for you? A few thousand messages per second is comfortably in RabbitMQ / ActiveMQ territory. Tens of thousands per second sustained, with partitioned ordering and replayability, is where Kafka starts to earn its operational complexity tax. Don’t pay that tax if you don’t need it. 🐳

The Operational Footnote

Kafka is more work to run than RabbitMQ. A production Kafka cluster needs at least three brokers, careful disk planning (it’s an append-only log; throughput is bounded by sequential write speed), partition-count decisions you can’t easily change later, and consumer-group rebalancing that misbehaves in subtle ways. Managed options — Confluent Cloud, Amazon Managed Streaming for Apache Kafka (MSK), Aiven — are how most teams avoid building this muscle in-house. 🛡️

RabbitMQ clusters are simpler. Three nodes, quorum queues for the data you really can’t lose, a mirrored management UI, and you’re done. The operational ceiling is lower but so is the floor.

ActiveMQ Artemis sits in between — easier than Kafka, slightly more configuration surface area than RabbitMQ, and a natural fit if your shop is already JMS-heavy.

Closing Thoughts

The Kafka-versus-RabbitMQ debate is mostly a category error. They aren’t competing products; they’re competing abstractions. Kafka is a log you happen to read like a queue. RabbitMQ is a queue with rich routing. ActiveMQ Artemis is a queue with deep JMS integration. Pick the abstraction your problem actually has, run it on the managed service if you can, and put the saved attention into your application code. 🎉

Further Reading

  • Kafka: The Definitive Guide (Confluent)confluent.io/resources/kafka-the-definitive-guide. The standard reference; covers partitions, replication, exactly-once semantics, Kafka Streams.
  • RabbitMQ documentationrabbitmq.com/documentation.html. Especially the “Reliability Guide” and “Quorum Queues” sections.
  • ActiveMQ Artemis documentationactivemq.apache.org/components/artemis/documentation/. The architecture and clustering chapters are worth the time.
  • Spring for Apache Kafkadocs.spring.io/spring-kafka. Listener containers, transaction management, error handling.
  • Spring AMQPdocs.spring.io/spring-amqp. The RabbitMQ-side equivalent.
  • “Kafka is not a queue” — Jay Kreps’ classic LinkedIn engineering post that explains the log abstraction better than any documentation does.
Posted in java | Tagged , , , , , | Comments Off on Apache Kafka vs RabbitMQ for Messaging in Java (and Where ActiveMQ Fits In)

Java Web Servers Compared: Tomcat, JBoss EAP, WildFly, and Spring Boot

If you’ve been writing Java for the web at any point in the last two decades, you’ve had to pick a web server or application server at least once. The choices haven’t changed much in name — Tomcat, JBoss, WildFly, and the relative newcomer Spring Boot are still the four most common answers — but the boundaries between them have. This post is a quick tour of each one and an honest table of when to reach for which. ☕

Servlet Container vs. Application Server

Before we get into the four contenders, one piece of vocabulary that trips people up. A servlet container implements the part of the Jakarta Enterprise Edition (Jakarta EE, formerly Java EE) specification that handles Hypertext Transfer Protocol (HTTP) requests, servlets, and JavaServer Pages (JSP). A full Jakarta EE application server implements that and the rest of the platform — Enterprise JavaBeans (EJB), Java Persistence Application Programming Interface (JPA), Java Message Service (JMS), Java Transaction API (JTA), Contexts and Dependency Injection (CDI), Java API for RESTful Web Services (JAX-RS), and so on.

Tomcat is a servlet container. JBoss Enterprise Application Platform (EAP) and WildFly are full Jakarta EE servers. Spring Boot is something different — it skips the container/server distinction entirely and ships an embedded server inside your application JAR. We’ll come back to that. 💡

Apache Tomcat

Tomcat is the original, the workhorse, the one that has been quietly running half the Java web in production since 1999. It’s developed under the Apache Software Foundation, implements the servlet, JSP, WebSocket, and Expression Language specs, and does not implement the heavier Jakarta EE pieces (no EJB, no JMS, no JTA out of the box).

That smaller surface area is its biggest feature. Tomcat starts in seconds, the configuration files (server.xml, web.xml) are short and well-understood, and the operational model is boring in the best sense — you drop a Web Application Archive (WAR) into webapps/ and a few seconds later the app is live.

1
2
3
4
5
wget https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.18/bin/apache-tomcat-10.1.18.tar.gz
tar xzf apache-tomcat-10.1.18.tar.gz
cd apache-tomcat-10.1.18
cp ~/Downloads/my-app.war webapps/
./bin/catalina.sh run

If your app needs EJB or JMS, you’ll be wiring in Spring or Apache Camel or a standalone broker to fill those gaps. At which point you’re effectively building a half-application-server out of libraries — which is exactly the gap Spring Boot exists to close.

JBoss EAP

JBoss EAP is Red Hat’s commercial Jakarta EE application server. It’s the supported, certified, contractually-backed product version of WildFly. Same codebase, slower release cadence, security patches backported for years, paid support attached. You buy JBoss EAP when you have a procurement department that wants someone to sue if the server crashes at 2 a.m.

From a developer perspective, JBoss EAP is WildFly — the management console looks the same, the standalone.xml configuration looks the same, the deployment commands look the same. The difference is the support contract and the slower, hardened release line. 🛡️

WildFly

WildFly is the upstream open-source community version of JBoss EAP. It was renamed from JBoss Application Server in 2013 to make the distinction between the community product and the Red Hat product less confusing. (It mostly worked.) WildFly is fully compliant with Jakarta EE, supports MicroProfile out of the box for cloud-native APIs, and has a genuinely good administration story — the jboss-cli.sh tool can configure datasources, deploy WARs, change subsystem settings, and a hundred other things, scriptably.

1
2
./bin/standalone.sh -c standalone-full.xml
./bin/jboss-cli.sh --connect --command="deploy /path/to/my-app.war"

If you actually need EJB, JMS via HornetQ/Artemis, distributed transactions, or the rest of the Jakarta EE stack, WildFly delivers all of it without you having to assemble it from libraries. The cost is startup time (tens of seconds, not single digits) and configuration surface area (standalone.xml is a thousand-plus lines on a fresh install).

Spring Boot

Spring Boot is the answer to the question: what if we just put the server inside the app? You write a main() method, the framework boots an embedded Tomcat (or Jetty, or Undertow) on a port, and your application starts handling requests. No external server to install, no WAR to deploy, no separate lifecycle to manage. You build a single executable JAR and run it like any other Java program.

1
2
3
4
5
6
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}
1
java -jar my-app.jar

This is the model that won the last decade. It fits perfectly with containers — one JAR, one process, one port, one health endpoint — and it removes an entire category of operational tickets (“why does our staging Tomcat have a different version than production?”). 🐳

Under the hood, Spring Boot defaults to embedded Tomcat. You can switch to embedded Jetty or Undertow by swapping a starter dependency. None of that changes your application code.

Side-by-Side Comparison

The honest summary:

Aspect Tomcat JBoss EAP WildFly Spring Boot
Type Servlet container Jakarta EE app server (commercial) Jakarta EE app server (community) Embedded-server framework
License Apache 2.0 Commercial (Red Hat) LGPL 2.1 Apache 2.0
Vendor Apache Software Foundation Red Hat / IBM Red Hat / community Broadcom / VMware Tanzu
Jakarta EE compliance Web Profile only (Servlet, JSP, WebSocket, EL) Full Platform Full Platform + MicroProfile Not certified; uses pieces via Spring
EJB / JMS / JTA No (add libraries) Yes Yes No (use Spring equivalents or libraries)
Deployment unit WAR dropped into webapps/ WAR / EAR via CLI or console WAR / EAR via CLI or console Executable JAR (or WAR if you must)
Startup time Seconds Tens of seconds Tens of seconds Seconds
Memory footprint Light (~150 MB) Heavy (500 MB+) Heavy (500 MB+) Light to moderate
Config style server.xml, web.xml standalone.xml, jboss-cli, web console standalone.xml, jboss-cli, web console application.properties / application.yml
Hot redeploy Reload context Yes, via CLI Yes, via CLI DevTools restart in dev
Container fit Good Workable, heavy Workable, heavy Excellent (one JAR, one process)
Commercial support Third parties (Tomitribe, etc.) Red Hat subscription Community / Red Hat Broadcom / Tanzu / third parties
Best fit Simple servlet/JSP or Spring-based apps Regulated enterprises needing certified Jakarta EE Teams wanting full Jakarta EE without paying New cloud-native services

So Which Do You Pick?

For a new service in 2022 with no legacy constraints, Spring Boot is the default answer. You get the productivity of a modern framework, an embedded server that fits the container model, and an ecosystem that covers virtually every integration you’ll need. The friction is low and the talent pool is huge. 🎉

If you’re maintaining an existing Tomcat deployment, there’s rarely a reason to migrate just for the sake of it — Tomcat is still excellent at what it does, and a Spring application running inside a standalone Tomcat is a perfectly valid setup. The migration to Spring Boot’s embedded model is a refactor you do when it pays for itself, not a religious obligation.

JBoss EAP and WildFly remain the right answer when you genuinely need the Jakarta EE Full Platform — distributed transactions across multiple resources, message-driven EJBs, two-phase commit between a database and a JMS queue. If that sentence sounds like your domain, you already knew. If it doesn’t, you probably don’t need them.

Closing Thoughts

The boundary between “servlet container” and “application server” mattered a lot in 2005. Today, with Spring Boot doing most of what an app server used to do via libraries, and Jakarta EE app servers slimming down to compete (WildFly’s wildfly-jar-maven-plugin can build a single fat JAR these days), the four contenders overlap more than they diverge. Pick the one your team already knows, unless there’s a specific feature pulling you elsewhere. 🛠️

Further Reading

  • Apache Tomcat documentationtomcat.apache.org. Configuration reference for whichever Tomcat major version you’re running.
  • WildFly documentationdocs.wildfly.org. Admin guide, model reference, and the jboss-cli tutorial.
  • Red Hat JBoss EAP documentationaccess.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform. Production hardening, clustering, and the support matrix.
  • Spring Boot reference documentationdocs.spring.io/spring-boot. The authoritative reference for the version you’re on.
  • Jakarta EE specificationsjakarta.ee/specifications. The actual standards each of these servers claims to implement.
Posted in java | Tagged , , , , , | Comments Off on Java Web Servers Compared: Tomcat, JBoss EAP, WildFly, and Spring Boot

Starting a Spring Boot API Microservice From Scratch With Spring Initializr

The fastest way to get a new Java microservice off the ground is also the most boring one, and that’s a compliment. You go to start.spring.io, click a few checkboxes, download a zip, and you have a runnable Hypertext Transfer Protocol (HTTP) service in under five minutes. No archetype incantations, no copy-pasting a pom.xml from a colleague’s repo, no “wait, which version of Spring Cloud goes with which Boot?” — Initializr handles compatibility for you. ☕

What We’re Building

A small, API-laden microservice — call it orders-service. It exposes a handful of Representational State Transfer (REST) endpoints, talks Java Script Object Notation (JSON), persists to a database, validates inputs, exposes health checks, and publishes an interactive Swagger / OpenAPI page that lets you try the endpoints from a browser. The goal isn’t a particular business problem — it’s a clean skeleton you can fork the next time someone says “we need a new service for X.”

Generate the Project

Head to https://start.spring.io. The form is small but every choice matters, so let’s walk through it.

  • Project: Maven. Gradle is fine too; pick whichever your team already uses. The rest of this post shows Maven snippets.
  • Language: Java.
  • Spring Boot version: the latest non-snapshot release. Avoid the SNAPSHOT and M (milestone) entries — they’re moving targets.
  • Group / Artifact / Name: com.acme, orders-service, orders-service. Group becomes the base package; artifact becomes the JAR name.
  • Packaging: Jar. Wars only make sense if you’re deploying into an existing servlet container, which you shouldn’t be.
  • Java version: the latest Long-Term Support (LTS) release available — 17 at time of writing. Stay on LTS for production.

Then the dependency list. For an API-laden service, click the Add Dependencies button and pick:

  • Spring Web — REST controllers, embedded Tomcat, JSON via Jackson.
  • Spring Data JPA — repository abstraction over the database.
  • PostgreSQL Driver — or your database of choice (MySQL Driver, H2 for in-memory testing).
  • Validation — Jakarta Bean Validation (the @Valid, @NotNull, @Size annotations).
  • Spring Boot Actuator — health, metrics, info endpoints.
  • Spring Boot DevTools — auto-restart on classpath changes during local dev.
  • Lombok — kill the getter/setter/builder boilerplate.
  • Testcontainers — run a real PostgreSQL in tests instead of mocking the repository layer.

Click Generate. You get a zip. Unzip it, cd in, and you can already run the service:

1
2
unzip orders-service.zip && cd orders-service
./mvnw spring-boot:run

That’s it. You’ll see Tomcat start on port 8080 and the embedded actuator endpoints come alive. 🎉

Add Swagger / OpenAPI Manually

Initializr doesn’t ship a Swagger checkbox, so add it by hand. The community-maintained library that plays nicely with current Spring Boot is springdoc-openapi. In your pom.xml:

1
2
3
4
5
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.3.0</version>
</dependency>

Restart the app and visit http://localhost:8080/swagger-ui.html. The library scans your @RestController classes, reads their annotations, and generates an interactive page where you can click Try it out and fire real requests. The raw OpenAPI 3 spec is served at /v3/api-docs in JSON, which is what you give to consumers who want to generate clients. 📘

Avoid the older springfox library you’ll find in many Stack Overflow answers — it has not kept up with current Spring Boot and you’ll fight startup errors. springdoc-openapi is the answer.

A Minimal Working Endpoint

Initializr drops a OrdersServiceApplication.java with the @SpringBootApplication annotation. Add an entity, a repository, a Data Transfer Object (DTO), and a controller next to it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
@Table(name = "orders")
@Getter @Setter
@NoArgsConstructor @AllArgsConstructor
public class Order {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    private String customerEmail;

    @NotNull @Positive
    private BigDecimal amount;

    private Instant createdAt = Instant.now();
}
1
2
3
public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByCustomerEmail(String customerEmail);
}
1
2
3
4
public record CreateOrderRequest(
    @NotBlank @Email String customerEmail,
    @NotNull @Positive BigDecimal amount
) {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {

    private final OrderRepository repository;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Order create(@Valid @RequestBody CreateOrderRequest request) {
        return repository.save(new Order(
            null, request.customerEmail(), request.amount(), Instant.now()
        ));
    }

    @GetMapping("/{id}")
    public Order get(@PathVariable Long id) {
        return repository.findById(id)
            .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
    }

    @GetMapping
    public List<Order> list(@RequestParam(required = false) String customerEmail) {
        return customerEmail == null
            ? repository.findAll()
            : repository.findByCustomerEmail(customerEmail);
    }
}

That’s a real, validated, persistent endpoint in under fifty lines. Lombok handles the boilerplate, Spring Data writes the SQL, and Jakarta Validation rejects bad payloads before they ever reach your code.

Wire Up the Database

The default application.properties Initializr drops is empty. Add database settings, but read them from environment variables so you can deploy the same artifact to multiple environments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring.application.name=orders-service
server.port=${PORT:8080}

spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:5432/orders}
spring.datasource.username=${DB_USER:orders}
spring.datasource.password=${DB_PASSWORD:orders}

spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.jdbc.time_zone=UTC

management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.probes.enabled=true

springdoc.swagger-ui.path=/swagger-ui.html
sprindoc.api-docs.path=/v3/api-docs

Two important defaults to flag:

  • spring.jpa.hibernate.ddl-auto=validate — validates that the schema matches your entities at startup, but does not create or modify tables. Use a real migration tool (Flyway or Liquibase) for that.
  • management.endpoint.health.probes.enabled=true — exposes /actuator/health/liveness and /actuator/health/readiness, which are exactly what Kubernetes wants. 🐳

Add Flyway for Migrations

Add to pom.xml:

1
2
3
4
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

Then create src/main/resources/db/migration/V1__create_orders.sql:

1
2
3
4
5
6
7
8
CREATE TABLE orders (
    id              BIGSERIAL PRIMARY KEY,
    customer_email  VARCHAR(255) NOT NULL,
    amount          NUMERIC(12, 2) NOT NULL CHECK (amount > 0),
    created_at      TIMESTAMP NOT NULL DEFAULT now()
);

CREATE INDEX idx_orders_customer_email ON orders(customer_email);

Flyway runs on app startup, applies any pending migrations, and refuses to start if a previously-applied migration’s checksum has changed. That last property is the single most useful safety net in Java backend development — it makes “I changed the migration after it was applied somewhere” loud instead of silent. 🛡️

A Quick Test With Testcontainers

Real database tests are a hill worth dying on. @DataJpaTest with the in-memory H2 driver tests Hibernate’s idea of your database, not the actual database. Testcontainers spins up a real PostgreSQL in Docker, points your test at it, and tears it down when you’re done:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@SpringBootTest
@Testcontainers
class OrderControllerTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");

    @DynamicPropertySource
    static void props(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url",      postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired private TestRestTemplate restTemplate;

    @Test
    void rejectsInvalidPayload() {
        var response = restTemplate.postForEntity(
            "/api/orders",
            Map.of("customerEmail", "not-an-email", "amount", -5),
            String.class
        );
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
    }
}

Slow on first run (Docker pulls the image), fast on every subsequent run.

Dockerize It

Spring Boot has built-in image building. No Dockerfile needed:

1
2
./mvnw spring-boot:build-image \
    -Dspring-boot.build-image.imageName=acme/orders-service:0.1.0

That uses Cloud Native Buildpacks under the hood to produce a small, layered image you can docker run immediately. If you’d rather hand-roll a Dockerfile for fine-grained control, the multi-stage version is:

1
2
3
4
5
6
7
8
9
10
FROM eclipse-temurin:17-jdk AS build
WORKDIR /app
COPY . .
RUN ./mvnw -B package -DskipTests

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/orders-service-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

The Final Library Cheat Sheet

If you’re starting a new API-laden Spring Boot service today and you want a sensible default stack, pick these:

  • spring-boot-starter-web — REST + embedded Tomcat.
  • spring-boot-starter-data-jpa + the relevant JDBC driver — persistence.
  • spring-boot-starter-validation — request validation.
  • spring-boot-starter-actuator — health, metrics, probes.
  • springdoc-openapi-starter-webmvc-ui — Swagger UI + OpenAPI 3 spec.
  • lombok — boilerplate killer.
  • flyway-core — versioned schema migrations.
  • testcontainers (postgresql, junit-jupiter) — real-database integration tests.
  • spring-boot-starter-security — when you need authentication (often the next thing you’ll add).

Closing Thoughts

Spring Initializr does its best work when you treat it as a starting point, not a destination. Generate the project, run it once to confirm it boots, then add the one or two libraries Initializr doesn’t include (Swagger, sometimes Flyway), wire up a real database, and you’re at the point where you can start writing actual business logic. The whole arc from “empty browser tab” to “first endpoint live in Docker” is comfortably under an afternoon. 💡

Further Reading

  • Spring Initializrstart.spring.io. The actual tool. The web version, the IDE integrations (IntelliJ, VS Code), and the underlying REST API all live here.
  • Spring Boot reference documentationdocs.spring.io/spring-boot/docs/current/reference/html/. Always the authoritative source for the version you’re on.
  • springdoc-openapi documentationspringdoc.org. Configuration, annotations, and migration notes from springfox.
  • Testcontainers for Javajava.testcontainers.org. Containers for PostgreSQL, MySQL, Kafka, Redis, and just about anything else you might integrate with.
  • Flyway documentationflywaydb.org/documentation. Naming conventions, callbacks, and CI integration.

Happy bootstrapping. 🛠️

Posted in java | Tagged , , , | Comments Off on Starting a Spring Boot API Microservice From Scratch With Spring Initializr

Why Ember.js Still Makes Sense for Big Teams Building Big Apps

In a JavaScript world dominated by React’s flexibility and Vue’s friendliness, Ember.js can feel like the quiet older sibling who keeps showing up to work in a suit. It’s opinionated, batteries-included, and unapologetically convention-driven. Which is exactly why some of the largest engineering teams in the world — LinkedIn, Apple Music’s web UI, Intercom, Square’s dashboard, Discourse — still bet on it. 🔥

What Is Ember.js?

Ember is a framework, not a library. The distinction matters. React gives you a rendering primitive and lets you pick the router, state manager, testing harness, build tool, data layer, and folder layout yourself. Ember gives you all of that in one box: router, components, data layer (Ember Data), testing framework (QUnit), build pipeline (Ember CLI), and a strong convention for where every file goes.

It’s the same philosophy as Ruby on Rails: convention over configuration. If you accept the conventions, you write less code and onboard new engineers faster. If you fight the conventions, you have a bad time.

Why Ember? (And Why Now?)

The case for Ember is rarely “it’s the fastest” or “it has the smallest bundle” — those are not its strengths. The case is about cost over time:

  • Stability without stagnation — Ember follows a six-week release cadence with strict semver and a deprecation pipeline. Upgrades from version to version are mostly mechanical, not rewrites. Long-running apps survive 5+ years on Ember without the “big rewrite” you see in React projects every 18 months.
  • One way to do things — there’s a canonical place for routes, components, models, services, and adapters. New hire on day one knows where to look.
  • First-class testing — every ember-cli generator scaffolds a test file alongside the source. Unit, integration, and acceptance tests are first-class citizens, not an afterthought.
  • Ember Data — a JSON:API client baked in. Identity-mapped, cached, with relationships and dirty tracking out of the box. You don’t roll your own data layer.

Is It Better for Bigger Teams?

Yes — and that’s not a coincidence, it’s the design goal. Ember’s opinions act as a coordination tax that pays back at scale.

On a 5-person React team, freedom is a feature. Everyone knows the codebase, conventions emerge organically, and the team can pivot when something better comes along. On a 50-person Ember team across multiple squads, freedom becomes friction. Squad A’s state management decision becomes Squad B’s onboarding pain. Ember sidesteps that whole class of debate — there is a state management story (@tracked, services), there is a routing story, there is a data layer story. Squads argue about features instead of stacks.

This is why LinkedIn’s web app, with hundreds of engineers, ships on Ember. The opinions scale; the decisions don’t multiply with headcount. 💡

The Architecture Pattern

The most common production shape for an Ember app: Ember owns the entire UI as a Single-Page Application (SPA), the backend is sliced into microservices, and Ember Data adapters glue them together over JSON:API.

1
2
3
4
5
6
7
8
9
┌───────────────────────────────────────────────┐
│              Ember.js SPA (browser)           │
│   Routes · Components · Services · Ember Data │
└──────────────┬────────────────────────────────┘
               │ JSON:API over HTTPS
      ┌────────┼────────┬────────────┬─────────┐
      ▼        ▼        ▼            ▼         ▼
   users     billing  catalog   notifications  search
   (Go)      (Java)   (Node)    (Python)       (Elastic)

A typical setup:

  • One Ember app — the monolithic frontend — owns routing, layout, design system, and user experience end to end. No micro-frontend fragmentation.
  • Backend split into focused services (auth, billing, catalog, search, notifications), each exposing JSON:API endpoints.
  • Ember Data adapters per model — sometimes per service — handle URL conventions, auth headers, and error normalization.
  • A thin gateway or Backend-for-Frontend (BFF) in front, if request aggregation or auth fan-out is needed.

An adapter pointing at a specific microservice looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/adapters/invoice.js — billing service
import JSONAPIAdapter from '@ember-data/adapter/json-api';
import { inject as service } from '@ember/service';

export default class InvoiceAdapter extends JSONAPIAdapter {
  @service session;

  host = 'https://billing.api.acme.com';
  namespace = 'v1';

  get headers() {
    return {
      Authorization: `Bearer ${this.session.token}`,
      Accept: 'application/vnd.api+json',
    };
  }
}

And a component that fetches and renders invoices is just:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// app/components/invoice-list.js
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class InvoiceListComponent extends Component {
  @service store;
  @tracked invoices = [];

  constructor() {
    super(...arguments);
    this.load();
  }

  async load() {
    this.invoices = await this.store.findAll('invoice');
  }
}
1
2
3
4
5
6
{{! app/components/invoice-list.hbs }}
<ul>
  {{#each this.invoices as |invoice|}}
    <li>{{invoice.number}} — {{invoice.amount}}</li>
  {{/each}}
</ul>

That’s the whole component. No reducer, no slice, no thunk, no useEffect dance. The store handles caching, identity, and the network call.

Top 5 Ember Libraries Worth Knowing

A short tour of the addons that show up in almost every serious Ember production app. All of them are open source — click through and read the source, the Ember community’s code tends to be remarkably clean and well-tested. 📚

  1. ember-concurrency — Tasks instead of promises. Cancellation, debouncing, dropping, restartable behavior, all declarative. Once you’ve used it, raw promises feel primitive.
    Source: github.com/machty/ember-concurrency

  2. ember-data — Already mentioned, but worth listing. JSON:API client, identity map, relationships, dirty tracking. The reference implementation for how to talk to a REST/JSON:API backend from an SPA.
    Source: github.com/emberjs/data

  3. ember-power-select — A flexible, accessible, fully-themed select/typeahead component. Sounds boring; replaces about 800 lines of hand-rolled select logic in every app.
    Source: github.com/cibernox/ember-power-select

  4. ember-simple-auth — Authentication and session management. Pluggable authenticators (OAuth2 — the second version of Open Authorization, JWT, custom), session persistence, route mixins to gate access. The de facto auth layer.
    Source: github.com/mainmatter/ember-simple-auth

  5. ember-cli-mirage — A client-side API mock you wire into your dev server and acceptance tests. Define your backend’s shape in JavaScript, get a fake server with realistic latency and relationships. Invaluable when the backend microservice you depend on is still in flight.
    Source: github.com/miragejs/ember-cli-mirage

So Should You Pick Ember in 2020?

If you’re a solo developer building a side project, probably not — the conventions feel heavy when there’s no team to coordinate. If you’re a 3-person startup shipping a Minimum Viable Product (MVP) and you might pivot the whole stack next quarter, also probably not. (And yes, that’s Minimum Viable Product — not Most Valuable Player, and not Most Valuable Person. Tech acronyms collide constantly. 🏀)

But if you’re building an application that needs to be maintained by dozens of engineers across multiple squads for the next five years, and you’d rather argue about features than about folder structure — Ember is still one of the strongest bets you can make. The TypeScript story is getting better, Octane (the modern component model) has fixed most of the historical ergonomics complaints, and the upgrade treadmill is gentler than anything else in this space. 🚀

Posted in javascript | Tagged , , | Comments Off on Why Ember.js Still Makes Sense for Big Teams Building Big Apps

List open or listening ports

You started a service, you can’t tell whether it actually bound to its port, and you want to see what’s listening — or you want to find out which process is squatting on port 8080. Two one-liners, two operating systems:

macOS

1
lsof -nP -i4TCP

RedHat / CentOS 7

1
netstat -tulpn

What the flags do: lsof -nP turns off DNS and port-name resolution (so you see 192.168.1.5:443 instead of app-server.local:https — faster and unambiguous). -i4TCP filters to IPv4 TCP sockets. For netstat -tulpn: t = TCP, u = UDP, l = listening only, p = show the PID/process, n = numeric (no DNS).


A few useful additions.

On modern Linux, prefer ss over netstat. The net-tools package that ships netstat is largely deprecated — most distros have moved to iproute2‘s ss (socket statistics). It’s faster on busy machines (reads from netlink instead of /proc) and uses the same flags you already know:

1
ss -tulpn

If you’ve been muscle-memory-typing netstat for years, the migration is one character. Same flags, same shape, modern implementation.

Listening-only on macOS. lsof -i4TCP shows every TCP connection — listeners and established. To narrow to just the things accepting new connections, add -sTCP:LISTEN:

1
2
3
4
5
# All listening TCP sockets (IPv4 + IPv6)
lsof -nP -iTCP -sTCP:LISTEN

# Add UDP for the full picture
lsof -nP -iUDP

The question you actually want answered: “what’s on port 8080?” Three flavours of the same question:

1
2
3
4
5
6
7
8
# macOS / Linux
lsof -i :8080

# Linux (modern)
ss -tulpn | grep :8080

# Linux (also handy — kill-by-port)
sudo fuser -k 8080/tcp

The last one is the nuclear option: fuser -k kills whoever has the port. Useful when a stale process is holding it and you don’t care about graceful shutdown.

Run it as root if you want to see other users’ processes. Without sudo, lsof, netstat -p, and ss -p only show process names for processes you own. If you see a port listed as LISTEN but the PID column is blank, that’s the symptom — re-run with sudo and the owner pops out.

Windows. The closest equivalent on Windows is netstat -ano from cmd (the -o shows the PID; cross-reference in Task Manager or with tasklist /fi “PID eq 1234”). PowerShell users get something nicer — Get-NetTCPConnection returns proper objects you can pipe and filter:

1
Get-NetTCPConnection -State Listen | Select-Object LocalAddress, LocalPort, OwningProcess

Pair that with Get-Process -Id $pid to translate OwningProcess back to a process name. 🔌

Posted in Bash, Operating System | Comments Off on List open or listening ports

MongoDB Notes

If you’re storing binary files inside MongoDB, the convention is called GridFS. It splits each logical file into two collections: a metadata document and a sequence of binary chunks. This post is a cheat sheet for inspecting and tweaking those documents from the Mongo shell. 🍃

When using MongoDB to store files, we have two collections:

  1. The place where MongoDB stores the file metadata: store.files
  2. And the place where MongoDB stores the file content: store.chunks

Depending on the size of the file, one entry in store.files can point to many entries in store.chunks. The bigger the file, the more entries you’ll encounter.

1
2
3
4
5
6
7
8
// Show all / list all entries from store.files
db.getCollection('store.files').find({});

// Show only a particular entry from store.files
db.getCollection('store.files').find({ _id: ObjectId("5b02d232cbce1d07e08401c7") });

// The same can be used for store.chunks.
db.getCollection('store.chunks').find({});

The metadata fields in store.files can be augmented at query time (the new field exists only in the result, not in the database):

1
2
3
4
db.getCollection('store.files').aggregate([
    { $match: { _id: ObjectId("5b02d232cbce1d07e08401c7") } },
    { $addFields: { 'key_reference': '1234' } }
]);

Or we can do an update on store.files, which actually persists the new field into the database:

1
2
3
4
db.getCollection('store.files').updateMany(
    { _id: ObjectId("5b02d232cbce1d07e08401c7") },
    { $set: { 'key_reference': '1234' } }
);

A few useful additions.

Why files are split into chunks. MongoDB’s per-document hard limit is 16 MB. GridFS works around that by splitting any file larger than the chunk size into many small chunk documents and writing one metadata doc that links them together. The default chunk size is 255 KB, configurable per bucket. So a 10 MB upload becomes one *.files doc and roughly 40 *.chunks docs, all sharing the same files_id. To inspect that relationship for a specific file:

1
2
3
db.getCollection('store.chunks')
    .find({ files_id: ObjectId("5b02d232cbce1d07e08401c7") })
    .sort({ n: 1 });   // n is the chunk index, 0..N-1

The bucket name store.* is custom. The default GridFS bucket is named fs, so out of the box you’d see fs.files and fs.chunks. The bucket name is whatever the application set when it opened the GridFS handle. If your app uses store, replace fs with store in any docs example you find online.

Putting and getting files in the first place. The shell snippets above are for inspecting files that are already there — they don’t help you upload or download the binary content. For that, use the mongofiles CLI or the driver-level GridFS API:

1
2
3
4
5
6
7
8
# Upload
mongofiles --uri "mongodb://localhost/mydb" --prefix store put /path/to/file.pdf

# Download
mongofiles --uri "mongodb://localhost/mydb" --prefix store get file.pdf

# List
mongofiles --uri "mongodb://localhost/mydb" --prefix store list

From application code, every official driver has a GridFS class — GridFSBucket in Node and Java, GridFS in PyMongo, IGridFSBucket in C#. They handle the chunking and reassembly for you.

Don’t delete files by hand. A common pitfall: deleting a row from store.files directly leaves the matching chunks orphaned in store.chunks, slowly bloating the collection. Either use mongofiles delete <filename>, or your driver’s GridFSBucket.delete(fileId), both of which remove the metadata and the chunks atomically.

Should you actually use GridFS? A practical heads-up: if your files are bigger than 16 MB and you already use MongoDB, GridFS is a reasonable fit and keeps backups simple. But for most modern stacks, putting the bytes in object storage (S3, GCS, MinIO, R2) and keeping only a URL or key in MongoDB is cheaper, faster, and easier to scale. GridFS is most defensible when you genuinely want files transactionally co-located with the database — e.g. mobile/embedded scenarios, or when network egress to S3 is a non-starter. 💡

Posted in Database | Comments Off on MongoDB Notes

CentOS 6 repo Settings

To fix repo settings in CentOS 6

1. make sure there is no proxy or funny settings in
vi /etc/yum.conf

2. There are a couple of files within /etc/yum.repos.d/. Make sure the url are correct (accessible) and enabled=1
ll /etc/yum.repos.d/

3. Cleanup the repo, list and retest
yum –enablerepo=base clean metadata;
yum repolist all
yum search java-1.8.0-openjdk

Posted in Linux | Comments Off on CentOS 6 repo Settings

Show Linux Partition Tree Mountpoint and If SSD

1
lsblk -o TYPE,NAME,KNAME,UUID,MOUNTPOINT,SIZE,ROTA
Posted in Linux | Comments Off on Show Linux Partition Tree Mountpoint and If SSD

Setting log4j log level programmatically

Sometimes you don’t want to ship a log4j.properties file — you want to spin up logging in code. Useful inside unit tests, one-off debug runs, or anywhere you want to flip log levels at runtime. Here’s a self-contained setupLog4j() that wipes any existing config, installs a console appender with a pattern, sets the root level to DEBUG, and binds a logger for your class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

private static void setupLog4j() {
        System.out.println("setupLog4j");
        BasicConfigurator.resetConfiguration();
        // Start clean.
        Logger.getRootLogger().removeAllAppenders();
        // Create appender
        ConsoleAppender console = new ConsoleAppender();
        // Configure the appender
        String PATTERN = "%d --[ %p ] %l: %m%n";
        console.setLayout(new PatternLayout(PATTERN));
        console.activateOptions();
        console.setName("stdout");
        Logger.getRootLogger().setLevel(Level.DEBUG);
        BasicConfigurator.configure(console);
        LOG = Logger.getLogger(MinerTest.class);
}

Replace MinerTest.class with your own class — it’s just the logger name (Log4j conventionally uses the fully-qualified class name so output stays organised by package).


A few useful additions.

This is Log4j 1.x — and Log4j 1.x is end-of-life. The package above is org.apache.log4j. Log4j 1.x reached EOL in August 2015 and has unpatched CVEs against it. If you’re starting something new, use Log4j 2 (org.apache.logging.log4j) or SLF4J with Logback. Keep this snippet around as a recipe for legacy projects, but don’t pick Log4j 1.x for anything fresh.

Set the level for one package, not the whole app. Logger.getRootLogger().setLevel(Level.DEBUG) turns DEBUG on globally — that floods everything, including third-party libraries. Usually you only want DEBUG for your own code:

1
2
Logger.getLogger("com.acme.miner").setLevel(Level.DEBUG);
Logger.getLogger("org.springframework").setLevel(Level.WARN); // tame the framework

Same idea in Log4j 2. The API is different — there’s no BasicConfigurator; instead you talk to the LoggerContext / Configurator:

1
2
3
4
5
6
7
8
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;

// Set the level for one package at runtime:
Configurator.setLevel("com.acme.miner", Level.DEBUG);

// Or for the root logger:
Configurator.setRootLevel(Level.DEBUG);

For the full “build a config from scratch” equivalent, see Log4j 2’s ConfigurationBuilder — the API is more verbose but lets you compose appenders, layouts, and loggers programmatically.

Using SLF4J / Logback? If your codebase logs via org.slf4j.Logger with Logback under the hood (very common), you flip levels through Logback’s own classes — SLF4J itself has no level-setting API:

1
2
3
4
5
6
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;

Logger logger = (Logger) LoggerFactory.getLogger("com.acme.miner");
logger.setLevel(Level.DEBUG);

The cast from org.slf4j.Logger to ch.qos.logback.classic.Logger is the giveaway — SLF4J is just a facade; the level lives on the implementation. 🪵

Posted in java | Comments Off on Setting log4j log level programmatically

Print java stack trace from anywhere

Need to know which code calls a specific location? Dump the stack trace:

1
2
3
4
import org.apache.commons.lang3.exception.ExceptionUtils;

// ...somewhere in your method:
LOG.trace(ExceptionUtils.getStackTrace(new Throwable()));

You’re constructing a Throwable just to capture the current stack — you’re not throwing it. ExceptionUtils.getStackTrace turns the captured frames into a multi-line String that’s safe to hand to a logger. Make sure your log config has trace level enabled for whichever logger LOG belongs to, otherwise the line silently does nothing.


A few useful additions.

When this beats a breakpoint. You’d reach for this over a debugger when (a) you’re chasing a bug that only shows up in production, (b) the call happens in async / event-driven code where breakpoints are awkward, or (c) a method is called from many places and you want to know which path is firing. Sprinkle a few of these in, run the workload, then read the log.

No Apache Commons? Stdlib will do. ExceptionUtils lives in org.apache.commons.lang3, which is a separate dependency. If you don’t have it on the classpath you can fall back to plain JDK:

1
2
3
4
5
6
7
8
9
10
// Option 1 — quick and dirty, writes to stderr (not your logger):
new Throwable().printStackTrace();

// Option 2 — get the trace as a String you can log:
import java.io.StringWriter;
import java.io.PrintWriter;

StringWriter sw = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(sw));
LOG.trace(sw.toString());

Java 9+: StackWalker is the modern API. If you actually want to inspect the frames programmatically (instead of just dumping a blob of text), use StackWalker — it’s lazy, so it doesn’t eagerly materialize every frame the way new Throwable().getStackTrace() does:

1
2
3
4
5
6
7
import java.lang.StackWalker;
import java.lang.StackWalker.StackFrame;
import java.util.stream.Collectors;

String trace = StackWalker.getInstance()
        .walk(s -> s.map(StackFrame::toString).collect(Collectors.joining("\n")));
LOG.trace(trace);

For a one-line debug print though, the original Apache Commons one-liner is still hard to beat. 🪵

Posted in java | Comments Off on Print java stack trace from anywhere