Jakarta Messaging JMS Interview Questions and Answers
Master Jakarta Messaging and JMS with production-ready interview questions covering queues, topics, producers, consumers, message types, acknowledgement, persistence, transactions, retries, redelivery, dead-letter queues, idempotency, and senior-level best practices.
Introduction
Jakarta Messaging is the Jakarta EE specification for reliable asynchronous communication between application components.
It was previously known as the Java Message Service, or JMS.
Jakarta Messaging allows applications to communicate through a messaging provider without requiring the sender and receiver to be available at the same time.
It supports two primary messaging models:
- Point-to-point messaging using queues
- Publish-subscribe messaging using topics
Jakarta Messaging is commonly used for:
- Order processing
- Payment workflows
- Claim processing
- Notification delivery
- Audit events
- File-processing jobs
- System integration
- Background tasks
- Decoupling services
- Load leveling
A production-ready messaging design must consider:
- Delivery guarantees
- Acknowledgement
- Transactions
- Redelivery
- Duplicate messages
- Message ordering
- Dead-letter queues
- Idempotency
- Poison messages
- Observability
- Back pressure
- Security
This guide covers the 15 most frequently asked Jakarta Messaging and JMS interview questions with code examples, Mermaid diagrams, production scenarios, common mistakes, and senior-level recommendations.
Q1. What is Jakarta Messaging?
Answer
Jakarta Messaging is the standard Jakarta EE API for sending and receiving messages through a messaging provider.
It allows applications to communicate asynchronously.
Main Components
JMSContextJMSProducerJMSConsumerQueueTopicMessage- Message-driven consumers
- Messaging provider
Basic Architecture
flowchart LR
A[Producer Application] --> B[Messaging Provider]
B --> C[Consumer Application]
The producer sends a message to a destination.
The messaging provider stores and routes the message.
The consumer receives and processes it.
Basic Producer Example
import jakarta.annotation.Resource;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.jms.JMSContext;
import jakarta.jms.Queue;
@ApplicationScoped
public class OrderMessageProducer {
@Inject
private JMSContext jmsContext;
@Resource(
lookup = "java:/jms/queue/OrderQueue"
)
private Queue orderQueue;
public void sendOrderCreated(
String orderId
) {
jmsContext
.createProducer()
.send(
orderQueue,
orderId
);
}
}
Why Interviewers Ask This
Interviewers want to confirm that you understand asynchronous messaging and the role of a broker or messaging provider.
Production Example
An order API stores an order and sends a message so inventory, notification, and fulfillment processing can continue asynchronously.
Common Mistake
Describing Jakarta Messaging as a database queue implementation.
Senior Follow-up
Jakarta Messaging defines the programming contract, while providers implement storage, routing, persistence, clustering, and delivery behavior.
Q2. What was JMS?
Answer
JMS stands for Java Message Service.
It was the original Java EE messaging API.
The modern name is Jakarta Messaging, and modern code uses the jakarta.jms namespace.
Namespace Migration
Older code:
import javax.jms.Message;
import javax.jms.Queue;
Modern code:
import jakarta.jms.Message;
import jakarta.jms.Queue;
Evolution
flowchart LR
A[JMS] --> B[Java EE Messaging API]
B --> C[Transferred to Jakarta EE]
C --> D[Jakarta Messaging]
D --> E[jakarta.jms Namespace]
Migration Considerations
- Update imports
- Update dependencies
- Verify server support
- Verify messaging-provider compatibility
- Test message-driven consumers
- Review transaction integration
- Validate serialization compatibility
Common Mistake
Assuming only source imports need to change.
Senior Follow-up
Migration should verify broker clients, runtime configuration, message formats, transactions, and third-party libraries.
Q3. What is the difference between a Queue and a Topic?
Answer
A Queue supports point-to-point messaging.
A Topic supports publish-subscribe messaging.
Queue
Each message is consumed by one eligible consumer.
flowchart LR
A[Producer] --> B[Order Queue]
B --> C[Consumer 1]
B -. Another message .-> D[Consumer 2]
Topic
Each eligible subscriber can receive a copy of the published message.
flowchart LR
A[Publisher] --> B[Order Topic]
B --> C[Inventory Subscriber]
B --> D[Notification Subscriber]
B --> E[Analytics Subscriber]
Comparison
| Queue | Topic |
|---|---|
| Point-to-point | Publish-subscribe |
| One consumer per message | Multiple subscribers may receive message |
| Competing consumers | Independent subscribers |
| Work distribution | Event broadcasting |
| Good for jobs | Good for domain events |
Queue Use Cases
- File-processing jobs
- Payment work items
- Notification tasks
- Report generation
- Batch jobs
Topic Use Cases
- Order-created event
- Customer-updated event
- Fraud-alert event
- Configuration-change event
- Audit event
Common Mistake
Using a Topic when only one worker should process each message.
Senior Follow-up
Choose the destination model according to business semantics, not only technical convenience.
Q4. What is point-to-point messaging?
Answer
Point-to-point messaging uses a Queue.
A producer sends messages to the Queue, and one consumer processes each message.
Multiple consumers can compete for messages, allowing horizontal scaling.
Flow
sequenceDiagram
participant Producer
participant Queue
participant ConsumerA
participant ConsumerB
Producer->>Queue: Message 1
Producer->>Queue: Message 2
Queue->>ConsumerA: Message 1
Queue->>ConsumerB: Message 2
Important Characteristics
- One successful consumer per message
- Consumers may compete
- Messages may remain stored until consumed
- Useful for load distribution
- Consumer count can scale independently
Example
jmsContext
.createProducer()
.send(
processingQueue,
"JOB-1001"
);
Consumer
JMSConsumer consumer =
jmsContext.createConsumer(
processingQueue
);
String jobId =
consumer.receiveBody(
String.class
);
Production Example
A file-processing Queue distributes uploaded files among ten worker instances.
Common Mistake
Assuming one specific consumer always receives a particular message.
Senior Follow-up
If message ordering matters, competing consumers and redelivery behavior must be carefully designed.
Q5. What is publish-subscribe messaging?
Answer
Publish-subscribe messaging uses a Topic.
A producer publishes an event, and multiple subscribers can receive independent copies.
Flow
sequenceDiagram
participant Publisher
participant Topic
participant Inventory
participant Notification
participant Analytics
Publisher->>Topic: OrderCreated
Topic->>Inventory: OrderCreated copy
Topic->>Notification: OrderCreated copy
Topic->>Analytics: OrderCreated copy
Good Use Cases
- Domain event distribution
- Audit notifications
- Cache invalidation
- Analytics events
- Multi-channel notification
- Independent downstream workflows
Publisher Example
jmsContext
.createProducer()
.send(
orderEventTopic,
orderCreatedEvent
);
Durable Subscription
A durable subscription allows messages to remain available for a subscriber while it is temporarily disconnected.
Concept
flowchart TD
A[Subscriber Offline] --> B[Publisher Sends Event]
B --> C[Broker Stores Event for Durable Subscription]
C --> D[Subscriber Reconnects]
D --> E[Stored Event Delivered]
Common Mistake
Assuming every Topic subscriber automatically receives messages sent while it is offline.
Senior Follow-up
Durable subscription identity, lifecycle, cleanup, and broker storage must be managed deliberately.
Q6. What is a JMS Producer?
Answer
A JMS Producer creates and sends messages to a Queue or Topic.
In the simplified Jakarta Messaging API, a producer is represented by JMSProducer.
Example
@ApplicationScoped
public class PaymentEventProducer {
@Inject
private JMSContext jmsContext;
@Resource(
lookup = "java:/jms/topic/PaymentTopic"
)
private Topic paymentTopic;
public void publish(
PaymentCompletedEvent event
) {
jmsContext
.createProducer()
.setProperty(
"eventType",
"PAYMENT_COMPLETED"
)
.send(
paymentTopic,
event
);
}
}
Producer Responsibilities
- Choose destination
- Create message
- Add properties
- Configure delivery mode
- Configure priority
- Configure expiration
- Send message
Producer Flow
flowchart TD
A[Business Service] --> B[Create Event]
B --> C[JMS Producer]
C --> D[Set Headers and Properties]
D --> E[Send to Destination]
E --> F[Messaging Provider]
Example Properties
jmsContext
.createProducer()
.setPriority(7)
.setTimeToLive(60_000)
.setProperty(
"tenantId",
tenantId
)
.send(
destination,
messageBody
);
Common Mistake
Sending large business objects without defining a stable message contract.
Senior Follow-up
Message schemas should be versioned and independent of internal entity classes.
Q7. What is a JMS Consumer?
Answer
A JMS Consumer receives messages from a Queue or Topic.
Consumers may receive messages:
- Synchronously
- Asynchronously
- Through message-driven components
Synchronous Consumer
try (
JMSConsumer consumer =
jmsContext.createConsumer(
orderQueue
)
) {
OrderMessage message =
consumer.receiveBody(
OrderMessage.class,
5_000
);
}
Asynchronous Consumer
JMSConsumer consumer =
jmsContext.createConsumer(
orderQueue
);
consumer.setMessageListener(
message -> {
try {
String orderId =
message.getBody(
String.class
);
orderService.process(
orderId
);
} catch (JMSException exception) {
throw new RuntimeException(
exception
);
}
}
);
Message-Driven Consumer Concept
flowchart LR
A[Queue] --> B[Container-Managed Message Listener]
B --> C[Application Service]
C --> D[Database]
Consumer Responsibilities
- Receive message
- Validate schema
- Process idempotently
- Handle failure
- Acknowledge or roll back
- Record observability data
Common Mistake
Performing long or unsafe processing without timeout, retry, or failure handling.
Senior Follow-up
A consumer should assume messages may be delivered more than once.
Q8. What is a JMS Message?
Answer
A JMS Message is the object transmitted through the messaging provider.
It contains:
- Headers
- Properties
- Body
Message Structure
flowchart TD
A[JMS Message] --> B[Headers]
A --> C[Properties]
A --> D[Body]
B --> E[Message ID]
B --> F[Timestamp]
B --> G[Correlation ID]
B --> H[Expiration]
B --> I[Priority]
C --> J[Business Metadata]
D --> K[Payload]
Common Headers
JMSMessageIDJMSTimestampJMSCorrelationIDJMSReplyToJMSDestinationJMSDeliveryModeJMSExpirationJMSPriorityJMSRedelivered
Message Properties
message.setStringProperty(
"eventType",
"ORDER_CREATED"
);
message.setStringProperty(
"tenantId",
tenantId
);
Correlation ID
A correlation ID links related request and response messages.
message.setJMSCorrelationID(
requestId
);
Common Mistake
Putting all routing information inside the message body.
Senior Follow-up
Use headers and properties for transport metadata, and keep the body focused on the business contract.
Q9. What message types are supported?
Answer
Jakarta Messaging supports several message body types.
Common Types
TextMessageBytesMessageMapMessageObjectMessageStreamMessage- Generic
Message
Comparison
| Type | Body |
|---|---|
TextMessage |
String text |
BytesMessage |
Raw bytes |
MapMessage |
Key-value pairs |
ObjectMessage |
Serializable Java object |
StreamMessage |
Ordered primitive values |
Message |
Headers and properties only |
Text Message
TextMessage message =
jmsContext.createTextMessage(
"""
{
"orderId": 101,
"status": "CREATED"
}
"""
);
Map Message
MapMessage message =
jmsContext.createMapMessage();
message.setLong(
"orderId",
101L
);
message.setString(
"status",
"CREATED"
);
Production Recommendation
Structured text formats such as JSON are often preferred for interoperability and schema evolution.
Message-Type Decision
flowchart TD
A[Choose Message Body] --> B{Cross-language consumers?}
B -->|Yes| C[JSON or standardized binary format]
B -->|No| D{Need Java serialization?}
D -->|Yes| E[ObjectMessage with caution]
D -->|No| F[Text or Map Message]
Common Mistake
Using ObjectMessage with internal entities.
Senior Follow-up
Java serialization tightly couples producer and consumer classes and can create security, compatibility, and deployment problems.
Q10. What is message acknowledgement?
Answer
Acknowledgement confirms that a message was successfully received or processed.
The acknowledgement model determines when the broker may consider a message complete.
Common Modes
- Automatic acknowledgement
- Client acknowledgement
- Duplicates-allowed acknowledgement
- Transaction-based acknowledgement
Conceptual Flow
sequenceDiagram
participant Broker
participant Consumer
participant Database
Broker->>Consumer: Deliver message
Consumer->>Database: Process business operation
alt Successful
Consumer->>Broker: Acknowledge
Broker->>Broker: Remove or mark complete
else Failed
Consumer-->>Broker: No acknowledgement or rollback
Broker->>Consumer: Redeliver later
end
Client Acknowledgement
JMSContext context =
connectionFactory.createContext(
JMSContext.CLIENT_ACKNOWLEDGE
);
JMSConsumer consumer =
context.createConsumer(queue);
Message message =
consumer.receive();
process(message);
message.acknowledge();
Important Nuance
Client acknowledgement may acknowledge more than one delivered message depending on session behavior.
Common Mistake
Acknowledging before business processing completes.
Senior Follow-up
Acknowledgement timing should align with the point at which the business operation is durably complete.
Q11. What is message persistence?
Answer
Message persistence determines whether the messaging provider must retain a message reliably across failures.
Delivery Modes
- Persistent
- Non-persistent
Persistent Message
jmsContext
.createProducer()
.setDeliveryMode(
DeliveryMode.PERSISTENT
)
.send(
paymentQueue,
paymentMessage
);
Persistent Flow
flowchart TD
A[Producer Sends Persistent Message] --> B[Broker Stores Message Durably]
B --> C[Broker Confirms Send]
C --> D[Consumer Receives]
D --> E[Successful Acknowledgement]
E --> F[Broker Removes Message]
Non-Persistent Flow
flowchart TD
A[Producer Sends Non-Persistent Message] --> B[Broker Routes Message]
B --> C{Broker Failure Before Delivery?}
C -->|Yes| D[Message May Be Lost]
C -->|No| E[Consumer Receives]
Trade-Offs
| Persistent | Non-Persistent |
|---|---|
| Higher reliability | Higher throughput |
| More storage I/O | Lower storage cost |
| Better for critical events | Better for disposable events |
| Survives provider restart | May not survive failure |
Common Mistake
Using non-persistent delivery for payment or transaction events.
Senior Follow-up
Delivery mode should be selected according to business loss tolerance, not only throughput goals.
Q12. How do transactions work in Jakarta Messaging?
Answer
Messaging operations can participate in a transaction.
A transaction can coordinate:
- Message receive
- Database update
- Message send
- Acknowledgement
Transactional Consumer Flow
sequenceDiagram
participant Broker
participant Consumer
participant Database
Broker->>Consumer: Deliver message
Consumer->>Database: Update business data
Consumer->>Broker: Send follow-up event
alt Transaction succeeds
Consumer->>Database: Commit
Consumer->>Broker: Commit acknowledgement and send
else Transaction fails
Consumer->>Database: Rollback
Consumer->>Broker: Rollback and redeliver
end
Jakarta Transaction Example
@ApplicationScoped
public class OrderMessageHandler {
@Inject
private OrderService orderService;
@Inject
private JMSContext jmsContext;
@Resource(
lookup = "java:/jms/topic/OrderEventTopic"
)
private Topic orderEventTopic;
@Transactional
public void process(
OrderMessage message
) {
orderService.updateOrder(
message.orderId()
);
jmsContext
.createProducer()
.send(
orderEventTopic,
new OrderProcessedEvent(
message.orderId()
)
);
}
}
Transaction Benefits
- Message acknowledgement follows successful processing
- Failed work can trigger redelivery
- Database and messaging work may be coordinated
- Partial completion is reduced
Common Mistake
Calling an external REST API inside a long messaging transaction.
Senior Follow-up
Distributed transaction support varies. Many systems prefer transactional outbox and idempotent consumers instead of broad distributed transactions.
Q13. What is a Dead Letter Queue?
Answer
A Dead Letter Queue, or DLQ, stores messages that cannot be processed successfully after configured delivery attempts.
DLQ Flow
flowchart TD
A[Message Delivered] --> B[Consumer Processing]
B --> C{Success?}
C -->|Yes| D[Acknowledge]
C -->|No| E[Redelivery Attempt]
E --> F{Maximum Attempts Reached?}
F -->|No| B
F -->|Yes| G[Move to Dead Letter Queue]
G --> H[Alert and Investigation]
Reasons Messages Reach a DLQ
- Invalid schema
- Missing required data
- Repeated downstream failure
- Unsupported event version
- Business-rule rejection
- Poison message
- Consumer bug
DLQ Processing Requirements
- Capture failure reason
- Preserve original message
- Preserve message ID
- Preserve correlation ID
- Record retry count
- Provide operational alerts
- Support replay after correction
- Prevent uncontrolled automatic replay
Example DLQ Record
{
"originalMessageId": "ID:12345",
"eventType": "ORDER_CREATED",
"failureReason": "Unknown product code",
"deliveryCount": 5,
"failedAt": "2026-07-12T10:00:00Z"
}
Common Mistake
Treating the DLQ as permanent message storage without monitoring or ownership.
Senior Follow-up
Every DLQ requires an operational process, alerting, replay policy, and clear ownership.
Q14. How do retries and redelivery work?
Answer
When message processing fails, the messaging provider may redeliver the message.
The retry policy may include:
- Maximum delivery count
- Fixed delay
- Exponential backoff
- Destination-specific configuration
- DLQ routing
- Exception-based retry decisions
Redelivery Flow
sequenceDiagram
participant Broker
participant Consumer
participant Dependency
Broker->>Consumer: Delivery attempt 1
Consumer->>Dependency: Call service
Dependency-->>Consumer: Temporary failure
Consumer-->>Broker: Rollback
Broker->>Consumer: Delivery attempt 2 after delay
Consumer->>Dependency: Call service
Dependency-->>Consumer: Success
Consumer->>Broker: Commit
Redelivery Indicator
boolean redelivered =
message.getJMSRedelivered();
A provider may also expose delivery count through properties.
Retry Classification
Retriable Failures
- Temporary database outage
- Network timeout
- Dependency unavailable
- Broker interruption
- Lock timeout
Non-Retriable Failures
- Invalid schema
- Missing required business field
- Unsupported event version
- Permanent business rejection
Retry Decision
flowchart TD
A[Processing Failure] --> B{Temporary?}
B -->|Yes| C[Retry with Backoff]
B -->|No| D[Move to DLQ or Reject]
C --> E{Retry Limit Reached?}
E -->|No| F[Redeliver]
E -->|Yes| D
Common Mistake
Retrying every error indefinitely.
Senior Follow-up
Retry policies must distinguish transient failures from permanent poison messages.
Q15. What are senior-level Jakarta Messaging best practices?
Answer
Senior developers should design consumers for at-least-once delivery and operational failure recovery.
Best Practices
- Use explicit message contracts.
- Version message schemas.
- Keep payloads small.
- Avoid sending JPA entities.
- Use persistent delivery for critical events.
- Design consumers to be idempotent.
- Configure bounded retries.
- Separate transient and permanent failures.
- Use dead-letter queues.
- Add correlation and causation IDs.
- Validate message schema before processing.
- Keep message transactions short.
- Avoid remote calls inside long broker transactions.
- Monitor queue depth and consumer lag.
- Add processing timeouts.
- Secure destinations.
- Protect sensitive message data.
- Test duplicate delivery.
- Test consumer restart behavior.
- Document replay procedures.
Messaging Design Flow
flowchart TD
A[Business Event] --> B[Define Stable Message Contract]
B --> C[Choose Queue or Topic]
C --> D[Choose Delivery Guarantee]
D --> E[Design Idempotent Consumer]
E --> F[Define Retry Policy]
F --> G[Configure DLQ]
G --> H[Add Metrics and Alerts]
H --> I[Test Failure and Replay]
Senior Follow-up
Reliable messaging requires application-level idempotency and observability even when the broker provides durable delivery.
Jakarta Messaging Architecture
flowchart TD
A[REST API] --> B[Application Service]
B --> C[Database]
B --> D[JMS Producer]
D --> E[Messaging Provider]
E --> F[Queue Consumer]
E --> G[Topic Subscriber]
E --> H[Audit Subscriber]
F --> I[Processing Service]
G --> J[Notification Service]
H --> K[Audit Store]
Queue Architecture
flowchart LR
A[Producer 1] --> C[Work Queue]
B[Producer 2] --> C
C --> D[Consumer 1]
C --> E[Consumer 2]
C --> F[Consumer 3]
Each message is handled by one successful consumer.
Topic Architecture
flowchart LR
A[Order Service] --> B[Order Event Topic]
B --> C[Inventory Subscriber]
B --> D[Billing Subscriber]
B --> E[Notification Subscriber]
B --> F[Analytics Subscriber]
Each subscriber receives an independent event copy.
Producer Example
@ApplicationScoped
public class ClaimEventPublisher {
private final JMSContext jmsContext;
@Resource(
lookup =
"java:/jms/topic/ClaimEvents"
)
private Topic claimEvents;
@Inject
public ClaimEventPublisher(
JMSContext jmsContext
) {
this.jmsContext =
jmsContext;
}
public void publish(
ClaimCreatedEvent event
) {
jmsContext
.createProducer()
.setDeliveryMode(
DeliveryMode.PERSISTENT
)
.setProperty(
"eventType",
"CLAIM_CREATED"
)
.setProperty(
"schemaVersion",
"1"
)
.send(
claimEvents,
event
);
}
}
Consumer Example
@ApplicationScoped
public class ClaimEventConsumer {
private final ClaimProcessingService
claimProcessingService;
@Inject
public ClaimEventConsumer(
ClaimProcessingService
claimProcessingService
) {
this.claimProcessingService =
claimProcessingService;
}
@Transactional
public void handle(
ClaimCreatedEvent event
) {
claimProcessingService
.processClaim(
event.claimId()
);
}
}
Message-Driven Bean Example
import jakarta.ejb.ActivationConfigProperty;
import jakarta.ejb.MessageDriven;
import jakarta.jms.JMSException;
import jakarta.jms.Message;
import jakarta.jms.MessageListener;
@MessageDriven(
activationConfig = {
@ActivationConfigProperty(
propertyName =
"destinationLookup",
propertyValue =
"java:/jms/queue/OrderQueue"
),
@ActivationConfigProperty(
propertyName =
"destinationType",
propertyValue =
"jakarta.jms.Queue"
)
}
)
public class OrderMessageListener
implements MessageListener {
@Override
public void onMessage(
Message message
) {
try {
String orderId =
message.getBody(
String.class
);
process(orderId);
} catch (JMSException exception) {
throw new RuntimeException(
exception
);
}
}
private void process(
String orderId
) {
// Business processing
}
}
Message Contract Example
public record OrderCreatedEvent(
String eventId,
int schemaVersion,
Instant occurredAt,
Long orderId,
Long customerId,
BigDecimal total
) implements Serializable {
}
Recommended Metadata
- Event ID
- Schema version
- Occurrence timestamp
- Correlation ID
- Causation ID
- Tenant ID
- Producer name
- Business identifier
Message Contract Design
flowchart TD
A[Message Contract] --> B[Metadata]
A --> C[Business Payload]
B --> D[Event ID]
B --> E[Schema Version]
B --> F[Correlation ID]
B --> G[Timestamp]
C --> H[Business Identifier]
C --> I[Required Event Data]
Idempotent Consumer Pattern
Because messages may be delivered more than once, consumers should prevent duplicate business effects.
Example
@Transactional
public void process(
OrderCreatedEvent event
) {
boolean alreadyProcessed =
processedEventRepository
.existsByEventId(
event.eventId()
);
if (alreadyProcessed) {
return;
}
orderWorkflow.start(
event.orderId()
);
processedEventRepository.save(
new ProcessedEvent(
event.eventId(),
Instant.now()
)
);
}
Idempotency Flow
flowchart TD
A[Receive Message] --> B[Read Event ID]
B --> C{Already Processed?}
C -->|Yes| D[Skip Duplicate]
C -->|No| E[Execute Business Operation]
E --> F[Record Event ID]
F --> G[Commit]
Important
The business operation and processed-event record should commit atomically where possible.
At-Most-Once vs At-Least-Once vs Exactly-Once
| Delivery Model | Meaning |
|---|---|
| At-most-once | Message may be lost but not repeated |
| At-least-once | Message is retried but may be duplicated |
| Exactly-once effect | Business effect occurs once through coordination and idempotency |
Practical Reality
flowchart TD
A[Broker Delivers Message] --> B[Consumer Completes Business Work]
B --> C{Acknowledgement Reaches Broker?}
C -->|Yes| D[Complete]
C -->|No| E[Broker Redelivers]
E --> F[Consumer Must Detect Duplicate]
Exactly-once business behavior usually requires more than a broker guarantee.
Transactional Outbox Pattern
The outbox pattern prevents database updates and event publication from becoming inconsistent.
Problem
flowchart TD
A[Update Database] --> B[Commit]
B --> C[Publish Message]
C --> D{Publish Fails?}
D -->|Yes| E[Database Changed but Event Missing]
Outbox Solution
flowchart TD
A[Begin Database Transaction] --> B[Update Business Table]
B --> C[Insert Outbox Event]
C --> D[Commit Together]
D --> E[Outbox Publisher Reads Event]
E --> F[Send to Broker]
F --> G[Mark Event Published]
Example Tables
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
status VARCHAR(30) NOT NULL
);
CREATE TABLE outbox_events (
event_id VARCHAR(100) PRIMARY KEY,
aggregate_id BIGINT NOT NULL,
event_type VARCHAR(100) NOT NULL,
payload TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
published_at TIMESTAMP NULL
);
Request-Reply Messaging
JMS supports request-reply patterns using:
JMSReplyToJMSCorrelationID
Flow
sequenceDiagram
participant Requester
participant RequestQueue
participant Responder
participant ReplyQueue
Requester->>RequestQueue: Request with ReplyTo and CorrelationID
RequestQueue->>Responder: Deliver request
Responder->>ReplyQueue: Response with same CorrelationID
ReplyQueue->>Requester: Deliver response
Warning
Request-reply messaging creates temporal coupling and should not replace simpler synchronous APIs without a clear reason.
Message Selectors
Selectors filter messages using message headers and properties.
Example
JMSConsumer consumer =
jmsContext.createConsumer(
paymentQueue,
"region = 'US' AND priorityLevel >= 5"
);
Selector Flow
flowchart TD
A[Queue Messages] --> B[Selector Expression]
B --> C[Matching Messages]
B --> D[Non-Matching Messages Remain]
Good Uses
- Region routing
- Message type routing
- Priority class
- Tenant partitioning
Risks
- Broker-side scanning cost
- Complex operational behavior
- Hidden routing rules
- Incorrect selector syntax
Message Priority
JMS priority ranges conceptually from lower to higher priority.
jmsContext
.createProducer()
.setPriority(9)
.send(
urgentQueue,
event
);
Warning
Priority is not always a strict ordering guarantee.
Use separate destinations for strongly separated service classes when needed.
Message Expiration
Time-to-live prevents stale messages from being delivered indefinitely.
jmsContext
.createProducer()
.setTimeToLive(
300_000
)
.send(
notificationQueue,
message
);
Expiration Flow
flowchart TD
A[Message Sent] --> B[TTL Starts]
B --> C{Consumed Before Expiry?}
C -->|Yes| D[Process Message]
C -->|No| E[Message Expires]
Good Use Cases
- Time-sensitive notifications
- Temporary cache invalidation
- Short-lived status updates
Do not use expiration for events that must never be lost.
Message Ordering
Ordering can be affected by:
- Multiple producers
- Multiple consumers
- Redelivery
- Transactions
- Priorities
- Partitioning
- Broker implementation
Ordering Risk
sequenceDiagram
participant Producer
participant Queue
participant ConsumerA
participant ConsumerB
Producer->>Queue: Event 1
Producer->>Queue: Event 2
Queue->>ConsumerA: Event 1
Queue->>ConsumerB: Event 2
ConsumerB-->>Queue: Completes first
ConsumerA-->>Queue: Completes later
Business processing order may differ from send order.
Strategies
- Single consumer
- Partition by business key
- Sequence numbers
- Version checks
- Idempotent state transitions
- Reject stale events
Poison Message Handling
A poison message always fails because its content is invalid or unsupported.
Flow
flowchart TD
A[Poison Message] --> B[Consumer Fails]
B --> C[Broker Redelivers]
C --> B
C --> D{Retry Limit}
D -->|Reached| E[Dead Letter Queue]
Best Practices
- Limit retries
- Capture failure details
- Alert operations
- Preserve original payload
- Add manual or controlled replay
- Fix consumer or message data before replay
Retry with Exponential Backoff
Attempt 1: immediate
Attempt 2: 5 seconds
Attempt 3: 30 seconds
Attempt 4: 2 minutes
Attempt 5: DLQ
Flow
flowchart LR
A[Attempt 1] --> B[5 Second Delay]
B --> C[Attempt 2]
C --> D[30 Second Delay]
D --> E[Attempt 3]
E --> F[2 Minute Delay]
F --> G[Final Attempt]
G --> H[DLQ]
Backoff helps avoid overwhelming a failing dependency.
Queue Depth and Consumer Lag
Monitor:
- Current queue depth
- Oldest message age
- Enqueue rate
- Dequeue rate
- Consumer count
- Processing duration
- Redelivery rate
- DLQ count
Capacity Flow
flowchart TD
A[Producer Rate] --> B{Greater than Consumer Rate?}
B -->|Yes| C[Queue Depth Grows]
C --> D[Consumer Lag Increases]
D --> E[Scale Consumers or Reduce Load]
B -->|No| F[Queue Remains Stable]
Back Pressure
Messaging absorbs bursts, but it does not provide infinite capacity.
Strategies
- Limit producer rate
- Scale consumers
- Apply admission control
- Use message expiration where appropriate
- Pause non-critical producers
- Split workloads into separate queues
- Alert on message age
- Apply circuit breakers to downstream dependencies
Messaging Security
Protect:
- Broker connections
- Destinations
- Credentials
- Message data
- Administrative interfaces
- Replay operations
Security Layers
flowchart TD
A[Producer] --> B[TLS Connection]
B --> C[Authenticated Broker Session]
C --> D[Destination Authorization]
D --> E[Encrypted or Protected Payload]
E --> F[Authorized Consumer]
Best Practices
- Use TLS.
- Use least-privilege destination permissions.
- Rotate credentials.
- Avoid secrets in message properties.
- Encrypt sensitive payload fields.
- Audit DLQ replay.
- Separate administrative and application accounts.
Queue vs Topic Decision Matrix
| Requirement | Queue | Topic |
|---|---|---|
| One worker processes each item | Yes | No |
| Distribute jobs among workers | Yes | No |
| Notify many independent systems | No | Yes |
| Load leveling | Yes | Limited |
| Broadcast domain event | No | Yes |
| Independent subscriber retry | No | Yes |
| Work queue | Yes | No |
Synchronous vs Asynchronous Processing
| Synchronous | Asynchronous |
|---|---|
| Caller waits | Caller continues |
| Immediate response | Eventual completion |
| Simple failure feedback | Requires status tracking |
| Tight temporal coupling | Looser temporal coupling |
| Lower operational complexity | Requires broker operations |
| Suitable for fast requests | Suitable for background workflows |
Durable vs Non-Durable Subscription
| Durable | Non-Durable |
|---|---|
| Messages retained while subscriber offline | Offline messages may be missed |
| Requires stable subscription identity | Simpler lifecycle |
| Uses broker storage | Lower storage overhead |
| Suitable for important events | Suitable for live-only updates |
Message Schema Versioning
Versioned Contract
{
"eventId": "evt-1001",
"schemaVersion": 2,
"eventType": "ORDER_CREATED",
"occurredAt": "2026-07-12T10:00:00Z",
"payload": {
"orderId": 101,
"customerId": 501,
"total": 250.00
}
}
Evolution Best Practices
- Add optional fields.
- Avoid renaming existing fields.
- Preserve old meanings.
- Support older versions during migration.
- Validate schema version.
- Use compatibility testing.
- Document deprecation.
Consumer Processing Pattern
flowchart TD
A[Receive Message] --> B[Extract Metadata]
B --> C[Validate Schema]
C --> D[Check Duplicate Event]
D --> E{Already Processed?}
E -->|Yes| F[Acknowledge and Skip]
E -->|No| G[Execute Business Logic]
G --> H[Record Processing Result]
H --> I[Commit Transaction]
I --> J[Acknowledge Message]
Production Order Workflow
flowchart TD
A[Order API] --> B[Create Order]
B --> C[Store Order]
C --> D[Publish OrderCreated]
D --> E[Inventory Queue]
D --> F[Notification Topic]
D --> G[Analytics Topic]
E --> H[Reserve Inventory]
F --> I[Send Confirmation]
G --> J[Update Dashboard]
Failure-Recovery Architecture
flowchart TD
A[Main Queue] --> B[Consumer]
B --> C{Processing Result}
C -->|Success| D[Acknowledge]
C -->|Transient Failure| E[Retry Queue]
E --> F[Delay]
F --> A
C -->|Permanent Failure| G[Dead Letter Queue]
G --> H[Alert]
H --> I[Investigate and Correct]
I --> J[Controlled Replay]
J --> A
Common Jakarta Messaging Mistakes
- Sending entities as messages.
- Using
ObjectMessagewithout compatibility planning. - Assuming messages are delivered exactly once.
- Ignoring duplicate delivery.
- Acknowledging before processing completes.
- Retrying permanent failures.
- Using unlimited retries.
- Ignoring dead-letter queues.
- Sending oversized payloads.
- Depending on strict global ordering.
- Missing correlation IDs.
- Missing schema versions.
- Calling slow external systems inside long transactions.
- Ignoring consumer lag.
- Treating the broker as a database.
- Using topics when work distribution is required.
- Using queues when every subscriber needs a copy.
- Failing to monitor expired messages.
- Replaying DLQ messages without correction.
- Logging sensitive payloads.
Senior Messaging Checklist
Stable Message Contract
│
▼
Correct Queue or Topic
│
▼
Persistent Delivery When Required
│
▼
Idempotent Consumer
│
▼
Bounded Retry Policy
│
▼
Dead Letter Queue
│
▼
Correlation and Schema Version
│
▼
Lag and Failure Monitoring
│
▼
Controlled Replay
Interview Quick Revision
| Concept | Purpose |
|---|---|
| Jakarta Messaging | Standard messaging API |
| JMS | Former name |
| Queue | Point-to-point destination |
| Topic | Publish-subscribe destination |
| Producer | Sends messages |
| Consumer | Receives messages |
| Message | Headers, properties, and body |
| Acknowledgement | Confirms processing |
| Persistent Delivery | Survives provider failure |
| Transaction | Coordinates processing |
| Redelivery | Retries failed message |
| DLQ | Stores repeatedly failed message |
| Durable Subscription | Retains topic messages offline |
| Selector | Filters messages |
| Idempotency | Prevents duplicate effects |
Key Takeaways
- Jakarta Messaging is the standard Jakarta EE API for asynchronous message-based communication.
- JMS is the former name of Jakarta Messaging.
- Queues provide point-to-point delivery where one consumer successfully handles each message.
- Topics provide publish-subscribe delivery where multiple subscribers receive independent copies.
- Producers create and send messages, while consumers receive and process them.
- A message contains headers, properties, and a body.
- Text-based and schema-driven contracts are generally more interoperable than serialized Java objects.
- Acknowledgement should occur only after durable business processing succeeds.
- Persistent delivery is appropriate for critical business messages that must survive broker failures.
- Messaging transactions can coordinate receiving, database updates, acknowledgement, and follow-up sends.
- Failed messages may be redelivered, so consumers must be idempotent.
- Retries should distinguish temporary failures from permanent poison messages.
- Dead Letter Queues require alerts, ownership, investigation, and controlled replay procedures.
- Durable Topic subscriptions retain messages while subscribers are offline.
- Message ordering can be affected by concurrency, retries, priorities, and multiple consumers.
- Correlation IDs, event IDs, schema versions, and timestamps improve traceability and compatibility.
- The transactional outbox pattern helps prevent database updates and event publication from becoming inconsistent.
- Queue depth, message age, redelivery rate, processing latency, and DLQ count are critical production metrics.
- Senior developers design messaging systems around at-least-once delivery, idempotent processing, bounded retries, stable schemas, and operational recovery.