Programming in Java

Chapter 12
Networking, Web Services, and Distributed Systems

Post Graduate Program in Computer Science Engineering
Programming in Java   •   Post Graduate

12. Networking, Web Services, and Distributed Systems

Introduction and Context

Java applications rarely operate in isolation. A banking application communicates with payment gateways, a learning platform retrieves course information from remote services, and a logistics system exchanges events with warehouses, carriers, and customers. These interactions occur across networks in which latency, failure, partial availability, security threats, and inconsistent state must be expected rather than treated as exceptional.

Networking in Java includes low-level communication through sockets, application-level interaction through HTTP, and service integration through structured data formats such as JSON. At a larger scale, distributed systems coordinate several independently running processes that may be deployed on different machines and managed by different teams. This creates powerful opportunities for scalability and organizational flexibility, but it also introduces problems that do not arise in a single-process application.

This chapter develops a practical and conceptual foundation for implementing networked Java applications. It connects Java networking APIs with RESTful service design, API contracts, validation, resilience, messaging, event-driven processing, idempotency, and distributed transactions. The central design principle is that a network call is not an ordinary method call: it can be delayed, rejected, duplicated, reordered, or lost.

Learning Objectives

After completing this chapter, students should be able to:

1. Explain the architectural characteristics and challenges of distributed systems.

2. Implement basic client-server communication using Java sockets.

3. Develop HTTP clients and RESTful services using appropriate methods and status codes.

4. Design JSON-based API contracts with validation and meaningful error responses.

5. Apply timeout, retry, circuit-breaker, bulkhead, and fallback strategies responsibly.

6. Distinguish synchronous request-response communication from asynchronous messaging and event-driven processing.

7. Design idempotent operations and reason about consistency and distributed transactions.

8. Evaluate service architectures with respect to security, observability, governance, and operational risk.

From Local Calls to Distributed Interactions

A local method call normally has predictable control flow: the caller transfers execution to the callee and receives a result or exception. A remote call has a much less reliable character. It involves serialization, transport, routing, server processing, deserialization, and often several intermediary components. Any stage can fail.

A distributed system is a collection of computational components that communicate through a network and cooperate to provide a service. The components may share a common objective without sharing memory, clocks, or failure boundaries. Consequently, the system must address latency, concurrency, naming, partial failure, data consistency, security, and observability.

Two distinctions are especially useful. In synchronous communication, the caller waits for a response, as in an HTTP request. In asynchronous communication, the sender places a message or event into an intermediary and continues, while a consumer processes it later. Synchronous calls are often simpler for immediate queries; asynchronous communication can improve decoupling and resilience but introduces delivery, ordering, and eventual-consistency concerns.

A service boundary should represent a meaningful business capability rather than merely expose internal classes. A well-designed service owns a coherent set of data and rules, publishes a stable contract, and hides implementation details. Excessively fine-grained services create network overhead and operational complexity; excessively large services reduce independent deployability. This tension is one reason service decomposition requires architectural judgment.

Java Networking at the Transport Layer

The socket is the fundamental abstraction for two-way communication between processes. A server creates a `ServerSocket`, binds to a port, listens for connections, and accepts clients. A client creates a `Socket` connected to the server address and port. Streams associated with the socket carry bytes in both directions.

A minimal server must define a communication protocol. For example, a line-oriented protocol may specify that the client sends one UTF-8 line and the server returns one response line. Without such framing, the receiver cannot reliably determine where one message ends. Other framing strategies include fixed-length messages, length prefixes, delimiters, or higher-level protocols such as HTTP.

A conceptual server loop is:

```java

try (ServerSocket server = new ServerSocket(9090)) {

while (!Thread.currentThread().isInterrupted()) {

Socket client = server.accept();

executor.submit(() -> handle(client));

}

}

```

The handler should use try-with-resources, specify character encoding explicitly, validate input, and avoid blocking indefinitely. A production server also needs connection limits, read and write timeouts, logging, authentication where required, and controlled shutdown. One thread per connection may be acceptable for small systems, but thread pools or non-blocking I/O are preferable when concurrency is high. Java NIO provides channels, buffers, selectors, and asynchronous facilities for such workloads.

Sockets are appropriate when a specialized protocol or low-level control is required. They are not automatically the best choice for business APIs. HTTP offers standard semantics, tooling, intermediaries, authentication mechanisms, caching rules, and observability conventions, which usually make it more suitable for web services.

HTTP and RESTful Service Design

HTTP follows a request-response model. A request contains a method, target URI, headers, and possibly a body. The response contains a status code, headers, and an optional body. The protocol is application-independent, but good API design uses its semantics consistently.

Common methods include:

  • `GET` retrieves a representation and should not change server state.
  • `POST` commonly creates a subordinate resource or requests an action that is not naturally represented as replacement.
  • `PUT` replaces or creates a resource at a known URI and is generally idempotent.
  • `PATCH` applies a partial modification, subject to the chosen patch semantics.
  • `DELETE` removes a resource and should be designed to be idempotent where practical.

A resource-oriented URI might be `/customers/42/orders/17`, while query parameters can express filtering, pagination, or sorting, such as `/orders?status=PAID&page=2`. The representation is commonly JSON, although the resource itself is conceptually distinct from any one representation format.

Status codes communicate important outcomes. `200 OK` indicates successful processing with a response representation; `201 Created` indicates successful creation and should normally identify the new resource; `204 No Content` indicates success without a response body; `400 Bad Request` indicates malformed or invalid input; `401 Unauthorized` indicates missing or invalid authentication; `403 Forbidden` indicates insufficient permission; `404 Not Found` indicates that the requested resource is unavailable; `409 Conflict` represents a state conflict; `429 Too Many Requests` indicates rate limiting; and `500`-class responses indicate server-side failure.

REST is more than sending JSON over HTTP. Its useful characteristics include resource identification, stateless requests, representations, a uniform interface, and use of standard HTTP semantics. Statelessness means that each request contains the information needed for the server to process it; it does not mean that the system has no persistent state.

A Java HTTP client can be implemented using `java.net.http.HttpClient`:

```java

HttpClient client = HttpClient.newBuilder()

.connectTimeout(Duration.ofSeconds(3))

.build();

HttpRequest request = HttpRequest.newBuilder()

.uri(URI.create("https://api.example.com/customers/42"))

.timeout(Duration.ofSeconds(5))

.header("Accept", "application/json")

.GET()

.build();

HttpResponse<String> response = client.send(

request, HttpResponse.BodyHandlers.ofString());

```

The client must inspect the status code, validate the response body, and distinguish transport errors from application errors. A response with status 404 is not equivalent to a DNS failure or a timeout; each implies a different recovery decision.

JSON Contracts and Validation

JSON is popular because it is readable, language-neutral, and supported by mature Java libraries such as Jackson and JSON-B. Its convenience does not remove the need for an explicit contract. A contract should define field names, types, required and optional properties, formats, value ranges, nullability, error structures, and compatibility expectations.

For example, an order-creation request might contain `customerId`, `items`, and `currency`. The server should validate syntactic properties first, such as whether the body is valid JSON and whether a numeric field is actually numeric. It should then validate semantic rules, such as whether the customer exists, whether quantities are positive, and whether the requested currency is supported.

Validation should occur at the boundary of the service. It protects internal components from invalid data and produces predictable feedback to clients. A useful error response includes a stable error code, a human-readable message, a correlation identifier, and field-level details when appropriate. Error messages should not disclose stack traces, SQL statements, tokens, or sensitive personal information.

API evolution requires compatibility discipline. Adding an optional response field is often safer than renaming an existing field. Removing or changing the meaning of a field can break clients. Versioning may be performed through URI paths, headers, or content negotiation, but the preferred approach is to preserve compatibility wherever possible and retire old contracts through a communicated lifecycle.

Resilience: Designing for Failure

Resilience is the ability of a system to continue providing an acceptable service despite faults. It is not achieved by blindly retrying every failure. A retry can amplify load during an outage, and retrying a non-idempotent operation can create duplicate effects.

Timeouts are the first line of defense. A connection timeout limits time spent establishing a connection, while a request or read timeout limits waiting for a response. Timeout values should reflect service-level objectives and downstream behavior rather than be chosen arbitrarily.

Retries are appropriate for transient failures such as temporary network interruption or service unavailability, especially for idempotent operations. Exponential backoff with jitter prevents many clients from retrying simultaneously. A retry policy should define a maximum number of attempts, eligible errors, and a total time budget.

A circuit breaker monitors failures. When failures exceed a threshold, it opens and quickly rejects calls instead of sending traffic to an unhealthy dependency. After a recovery interval it enters a half-open state and permits limited test requests. A bulkhead isolates resources, such as assigning separate thread pools or concurrency limits to different dependencies. A fallback provides a reduced response, cached result, or explicit unavailability when the primary operation cannot be completed.

These patterns must be combined with observability. Metrics such as latency percentiles, error rate, timeout count, retry count, and circuit state reveal whether resilience mechanisms are helping or masking a deeper problem. Distributed tracing connects a user request across services by propagating a trace or correlation identifier.

Messaging and Event-Driven Processing

A message broker decouples producers and consumers. A producer sends a message to a queue or topic; a consumer receives and processes it. Queues often distribute work among competing consumers, whereas topics support multiple independent subscribers. Brokers can buffer traffic, support retries, and allow consumers to process at their own pace.

An event records that something happened, such as `OrderPlaced` or `PaymentAuthorized`. A command requests that an operation be performed, such as `AuthorizePayment`. Confusing these concepts can produce unclear ownership and accidental feedback loops. Events should contain sufficient information for their consumers, while avoiding unnecessary sensitive data.

Event-driven systems usually provide at-least-once delivery because guaranteeing exactly-once effects across independent systems is difficult. Therefore, consumers must be idempotent. A consumer can store a message identifier or business key in an inbox table before applying the effect. If the same message arrives again, the consumer recognizes it and avoids repeating the operation.

The transactional outbox pattern addresses a common failure: a service updates its database successfully but crashes before publishing the corresponding event. The service writes the business change and an outbox record in the same local transaction. A separate publisher reads unsent outbox records and delivers them to the broker, marking them as published. Duplicate publication remains possible, so consumers still need idempotency.

Ordering is normally guaranteed only within a defined scope, such as a partition or key. If all events for an aggregate must be processed sequentially, they should be routed using a stable aggregate key. Consumers should also handle late, duplicated, or out-of-order events.

Idempotency and Distributed Transactions

An operation is idempotent if repeating it produces the same intended result as performing it once. `PUT /accounts/7` with the same complete representation is commonly idempotent; a payment request that charges an account twice is not. Idempotency is a property of observable effects, not simply of the HTTP method.

An idempotency key allows a client to safely retry a request. The server stores the key, request identity, and resulting outcome. A repeated request with the same key returns the recorded result rather than executing the operation again. The server must define how long keys are retained and how it handles the same key used with a different payload.

Traditional ACID transactions are powerful within one database boundary. Across multiple services, a single atomic transaction is difficult because participants may fail independently and hold locks for long periods. Two-phase commit can coordinate participants, but it introduces blocking, operational complexity, and coupling.

The saga pattern divides a business transaction into local transactions connected by messages or orchestration. Each successful step has a compensating action. For example, an order workflow may reserve inventory, authorize payment, and arrange shipment. If shipment fails, the system may release inventory and void payment. Compensation is not always a perfect inverse: a notification cannot truly be unsent, and a refund may require separate processing.

Orchestration uses a coordinator that directs the steps and tracks state. Choreography lets services react to one another's events without a central coordinator. Orchestration offers clearer visibility for complex workflows; choreography can reduce central coupling but may become difficult to understand as event relationships multiply. Neither eliminates the need for durable state, retries, timeout handling, and reconciliation.

Security, Governance, and Professional Responsibility

Networked applications must use transport encryption, normally through HTTPS, to protect confidentiality and integrity. Authentication establishes identity; authorization determines permitted actions. These concerns should not be confused. A valid authenticated user may still be forbidden to access another customer's order.

API security also requires input validation, protection against injection, secure secret management, rate limiting, careful CORS configuration where applicable, and prevention of excessive data exposure. Tokens should have appropriate lifetimes and scopes. Logs must avoid passwords, access tokens, payment details, and unnecessary personal data.

Governance includes ownership of APIs, contract review, version policy, dependency management, vulnerability scanning, auditability, retention rules, and incident response. Regulatory obligations may require access controls, data minimization, deletion procedures, or localization controls depending on the domain and jurisdiction. Reliability claims should be supported by measurable service-level indicators and objectives.

Applied Case: An Online Retail Order Platform

Background

The following is an illustrative case. A retailer operates a Java-based order platform integrated with inventory, payment, shipping, and notification services. The original application makes synchronous calls to all four systems during checkout.

Situation and Problem

During promotional periods, checkout latency rises sharply. Payment providers occasionally time out after accepting a request, causing customers to retry and sometimes creating duplicate authorizations. Inventory updates are occasionally lost when the order service crashes after changing its database but before sending an inventory request. Support staff cannot trace a customer's request across services.

Analysis

The architecture combines long synchronous chains with inadequate timeouts and no end-to-end idempotency. A timeout does not reveal whether the downstream service performed the operation. The order database and external systems also have separate transaction boundaries, so local commit does not guarantee global completion.

Approach and Solution

The team first defines an API contract for order creation. The client supplies an idempotency key; the order service stores that key with the request and outcome. All HTTP clients receive connection and request timeouts, and retries are limited to transient failures. Payment authorization uses the idempotency key supplied by the order service.

The order service writes the new order and an outbox event in one database transaction. An outbox publisher sends `OrderCreated` to the broker. Inventory and payment workflows consume the event and record processed message identifiers. A saga coordinator tracks the order state: pending, inventory reserved, payment authorized, confirmed, or compensation required. Failed inventory reservation triggers payment voiding when necessary. A circuit breaker prevents an unhealthy notification service from blocking checkout.

The platform propagates a correlation identifier through HTTP headers and message metadata. Metrics measure checkout latency, failed steps, retry volume, duplicate-message detection, and compensation frequency. Logs are structured and exclude payment credentials.

Outcome

The redesigned workflow accepts an order without requiring every downstream system to be immediately available. Duplicate client submissions produce one order outcome. Delayed inventory or payment processing is visible through order status rather than being hidden behind indefinite waiting. The approach does not make failures disappear; it makes them explicit, recoverable, and observable.

Learning Points

The case demonstrates that resilience depends on coordinated contracts, idempotency, durable messaging, compensation, and observability. No individual framework feature can replace architectural reasoning about ownership, failure, and consistency.

Common Mistakes and Design Trade-offs

A frequent mistake is treating HTTP status codes as mere decoration and returning `200 OK` for validation failures or server exceptions. Another is retrying every exception, including authentication failures and non-idempotent operations. Infinite queues, unlimited thread pools, and unbounded request bodies can convert a dependency failure into resource exhaustion.

Microservices are not automatically superior to modular monoliths. They can improve independent deployment and team autonomy, but they add network calls, deployment pipelines, monitoring requirements, and distributed data management. A modular monolith may be the better starting point when domain boundaries are uncertain or operational capacity is limited.

Eventual consistency also requires user-interface and business-process decisions. A customer may see “payment pending” rather than an immediate final confirmation. Systems should provide status queries, reconciliation jobs, dead-letter handling, and operator procedures for unresolved workflows.

Current Developments and Best Practices

Modern Java services commonly use containerized deployment, orchestration platforms, managed brokers, API gateways, and centralized observability. Reactive and asynchronous programming can improve resource utilization for I/O-heavy workloads, but it increases programming complexity and should be adopted when workload characteristics justify it.

Service meshes can provide traffic policies, mutual TLS, retries, and telemetry outside application code, although they do not eliminate the need for correct application semantics. Contract testing helps verify that a provider and its consumers agree on API behavior. OpenAPI documents can support client generation, validation, and governance, but generated code cannot substitute for domain-level design.

Practical best practices include:

  • define contracts before implementation and test them automatically;
  • set explicit timeouts and bounded retries;
  • make externally visible effects idempotent;
  • use correlation identifiers, metrics, logs, and traces together;
  • isolate dependencies with concurrency limits and circuit breakers;
  • prefer local transactions plus outbox and saga techniques over fragile cross-service assumptions;
  • secure transport, identities, secrets, and sensitive data;
  • document ownership, compatibility, error semantics, and operational procedures.

Chapter Summary

Java networking ranges from socket-level byte streams to HTTP-based services and broker-mediated events. Distributed systems require explicit treatment of latency, partial failure, consistency, security, and observability. RESTful services should use meaningful resources, methods, status codes, JSON contracts, and boundary validation. Reliable clients use timeouts, bounded retries, backoff, circuit breakers, bulkheads, and fallbacks according to operation semantics. Messaging and event-driven processing improve decoupling but require durable delivery strategies, ordering decisions, and idempotent consumers. The outbox pattern connects local database changes to events, while sagas coordinate business workflows without assuming one global transaction. Professional service integration combines technical patterns with governance, security, contract evolution, monitoring, and operational accountability.

Key Terms

Socket: A software endpoint used for bidirectional network communication.

ServerSocket: A Java abstraction that listens for incoming TCP connections.

HTTP: An application-layer protocol supporting request-response communication.

REST: An architectural style using resources, representations, stateless interaction, and uniform interface semantics.

Idempotency: The property that repeating an operation produces the same intended effect as performing it once.

JSON: A text-based, structured data-interchange format.

API contract: A documented agreement defining requests, responses, errors, and compatibility rules.

Timeout: A limit on how long an operation may wait.

Circuit breaker: A resilience mechanism that temporarily blocks calls to an failing dependency.

Bulkhead: Isolation of resources so one dependency cannot exhaust the entire application.

Message broker: Infrastructure that receives, stores, routes, and delivers messages.

Event: A record that a significant state change has occurred.

At-least-once delivery: A delivery model in which a message may be delivered one or more times.

Transactional outbox: A pattern that stores a business change and its pending event in one local transaction.

Saga: A sequence of local transactions coordinated through events or an orchestrator.

Compensating action: An operation that semantically offsets a previously completed step.

Eventual consistency: A model in which replicas may temporarily differ but converge over time.

Distributed tracing: Tracking a request across multiple services using propagated identifiers.

Contract testing: Automated verification that service interactions conform to an agreed API contract.

Review Questions

Short Answer Questions

1. Why is a network call less reliable than a local method call?

2. Distinguish authentication from authorization in an HTTP service.

3. What problem does an idempotency key solve?

4. Explain the purpose of a transactional outbox.

5. What is the difference between an event and a command?

Long Answer Questions

1. Explain RESTful service design with reference to methods, resource URIs, status codes, and statelessness.

2. Discuss timeout, retry, circuit-breaker, bulkhead, and fallback patterns, including their risks.

3. Describe how JSON contracts and boundary validation support robust service integration.

4. Compare orchestration and choreography in saga-based distributed transactions.

5. Explain the major security and governance responsibilities involved in operating networked Java services.

Analytical / Application Questions

1. A payment request times out, but the provider may have processed it. Design a safe retry and idempotency strategy.

2. An order service commits its database update but crashes before publishing an event. Analyze the failure and propose a recovery architecture.

3. A service chain contains six synchronous dependencies and experiences cascading outages. Recommend an improved communication and resilience design, justifying each major choice.

22. Multiple Choice Questions with Answers

Question 1

A Java HTTP client receives a 404 response from a dependency. Which interpretation is most appropriate?

A. The network connection necessarily failed before reaching the server.

B. The requested resource was not available at the specified endpoint.

C. The client must retry indefinitely because the server is overloaded.

D. The response proves that authentication succeeded and authorization failed.

Correct Answer: B. The requested resource was not available at the specified endpoint.

Question 2

Which design most directly prevents duplicate payment authorization when a client retries after a timeout?

A. Returning HTTP 200 for every payment request

B. Using an idempotency key whose outcome is stored and reused

C. Increasing the client connection timeout without limit

D. Publishing the payment request only through a broadcast topic

Correct Answer: B. Using an idempotency key whose outcome is stored and reused

Question 3

What problem does the transactional outbox pattern primarily address?

A. Encrypting JSON fields during transport

B. Ensuring that a database change and the intent to publish an event are recorded atomically

C. Guaranteeing globally ordered events across all services

D. Eliminating the need for consumers to handle duplicate messages

Correct Answer: B. Ensuring that a database change and the intent to publish an event are recorded atomically

Question 4

A circuit breaker is open. What is its usual purpose?

A. To continue sending unlimited requests to a failing dependency

B. To reject calls temporarily and prevent further pressure on an unhealthy dependency

C. To replace all failed responses with successful responses

D. To force every operation into a distributed ACID transaction

Correct Answer: B. To reject calls temporarily and prevent further pressure on an unhealthy dependency

Question 5

Which statement best distinguishes an event from a command?

A. An event records something that happened, whereas a command requests an action.

B. An event is always synchronous, whereas a command is always asynchronous.

C. An event can contain no data, whereas a command must contain database credentials.

D. An event is used only internally, whereas a command must be public.

Correct Answer: A. An event records something that happened, whereas a command requests an action.

Question 6

Why must an at-least-once message consumer be idempotent?

A. Because the broker guarantees that messages arrive in reverse order

B. Because the same message may be delivered more than once

C. Because consumers cannot store any processing state

D. Because idempotency automatically provides encryption

Correct Answer: B. Because the same message may be delivered more than once

Question 7

Which approach is most suitable for coordinating a business workflow spanning inventory, payment, and shipping services without one global database transaction?

A. A saga with local transactions and compensating actions

B. A larger thread pool with no failure handling

C. A single HTTP 200 response from the order service

D. A client-side JSON formatting rule

Correct Answer: A. A saga with local transactions and compensating actions

Question 8

A service receives malformed JSON with a negative item quantity. Where should this input primarily be rejected?

A. At the service boundary through syntax and semantic validation

B. Only after the item is shipped

C. By the circuit breaker after three retries

D. By the distributed tracing system

Correct Answer: A. At the service boundary through syntax and semantic validation

Question 9

What is a major risk of retrying a non-idempotent operation after a timeout?

A. The operation may be applied more than once

B. The response will always become encrypted

C. The service will become stateless

D. The request URI will necessarily change

Correct Answer: A. The operation may be applied more than once

Question 10

Which observation most strongly indicates the need for distributed tracing?

A. A local variable has an unclear name

B. A user request crosses several services and its total delay cannot be localized

C. A JSON document contains an optional field

D. A server uses a `ServerSocket` on a private test machine

Correct Answer: B. A user request crosses several services and its total delay cannot be localized

Programming in Java
Chapter 12 of 15