DEV Community

Cover image for Java Global Stock Realtime Quotes: High Availability Architecture & Exception Handling for Financial Systems
San Si wu
San Si wu

Posted on

Java Global Stock Realtime Quotes: High Availability Architecture & Exception Handling for Financial Systems

1. Introduction: Core Pain Points in Global Stock Real-Time Quote Integration

In financial systems such as quantitative trading, financial market terminals, and intelligent investment advisory services, integrating global stock real-time quotes based on Java is a core foundational capability. Unlike ordinary business interfaces, global stock market data features high concurrency, low latency, 7×24 hour uninterrupted operation, unstable data sources, and significant cross-regional network fluctuations.

Overseas markets including US stocks, Hong Kong stocks, and European stocks face various issues such as time zone differences, exchange rate limiting, network jitter, packet loss, and interface circuit breaking. If the system only performs simple data fetching and parsing, it is prone to online failures such as data interruptions, market quote lag, dirty data penetration, and service avalanches.

This article will explain the practical implementation of global stock real-time quote systems from five dimensions: high-availability overall architecture design, core high-availability assurance solutions, full-link exception classification and handling, practical code implementation, and performance optimization, addressing the stability and reliability challenges of cross-market quote integration.

2. Overall High-Availability Architecture Design

Targeting the business characteristics of global stock real-time data, we abandon the simple single-point integration mode and adopt a high-availability architecture of layered microservices + multi-data-source redundancy + asynchronous decoupling. The overall architecture is divided into five layers to achieve fault isolation, horizontal scaling, and seamless disaster recovery, adapting to the 7×24 hour uninterrupted market quote service requirements.

2.1 Architecture Layer Overview

From upstream data sources to downstream business consumption, we achieve layer-by-layer decoupling and fault tolerance. The core layers are as follows:

  1. Data Source Access Layer: Taking iTick API as an example, it supports dual protocol integration of REST API (batch query/single retrieval) and WebSocket (low-latency real-time push). Covering global multi-asset classes, a single long connection can subscribe to up to 500 instruments, and multiple API Keys can be configured for multi-path redundancy to avoid single data source failure risks.
  2. Network Fault Tolerance Layer: Encapsulates connection pools, heartbeat detection, timeout control, and remote retry mechanisms to solve cross-border network latency, jitter, and disconnection issues, ensuring cross-border data transmission stability.
  3. Data Processing Layer: Completes market data processing, format unification, dirty data filtering, data validation, and quote aggregation. It unifies stock quote data formats across global markets and eliminates abnormal, expired, and invalid data.
  4. Cache and Storage Layer: Uses Redis clusters for real-time quote caching, local memory for hot data fallback, and time-series databases for historical quote storage, supporting high-concurrency queries and second-level data response.
  5. Business Distribution Layer: Pushes standardized real-time quotes to front-end terminals, quantitative strategies, and business services through WebSocket and MQ, achieving data subscription and asynchronous distribution while avoiding downstream services blocking upstream data collection.

2.2 Core High-Availability Design Highlights

  1. Multi-Data-Source Redundancy and Disaster Recovery: Configure primary and backup API Keys at two levels. When the primary account triggers rate limiting or encounters network anomalies, it automatically switches seamlessly to the backup account to ensure uninterrupted market quotes.
  2. Clustered Stateless Deployment: Market quote collection services are stateless, supporting Nginx load balancing and dynamic scaling. Node status is monitored in real-time through heartbeat mechanisms, with faulty nodes automatically removed to avoid single points of failure.
  3. Full-Link Asynchronous Decoupling: Based on Netty asynchronous I/O, thread pools, and message queues, the entire process of data collection, processing, and distribution is asynchronous, avoiding synchronous blocking delays and supporting the processing of tens of thousands of quote data per second.
  4. Graded Degradation and Circuit Breaking: Configure differentiated circuit breaking, degradation, and rate limiting strategies for different links such as data source interfaces, data processing, and message distribution to avoid overall service avalanches caused by single link failures.

3. Core High-Availability Architecture Implementation

3.1 Multi-Data-Source Redundancy and Automatic Switching

Financial systems must still consider extreme scenarios (such as account arrears, regional network failures, etc.). Therefore, the system designs a dynamic data source routing strategy: maintaining multiple API Keys, evaluating the health weight of each Key in real-time through periodic heartbeat detection, interface success rate statistics, and timeout count statistics. The Key with the highest health weight serves as the primary data source, automatically degrades to backup when the weight is too low, and is reintegrated into the available list after recovery.

3.2 Network Layer High-Availability Assurance

Cross-border networks are the most unstable factor in quote integration. To address cross-regional network latency, packet loss, and disconnection issues, we implement four-fold network fault tolerance design:

  1. Connection Pool Reuse: Use Apache HttpClient connection pools and Netty long connection pools to manage network connections, avoiding performance loss and connection timeout exceptions caused by frequent connection creation and destruction, improving cross-border request efficiency.
  2. Graded Timeout Control: Strictly differentiate link timeout times. Set core real-time quote link timeout to 500ms and non-core batch data link timeout to 2s to avoid ineffective blocking of thread resources.
  3. Remote Retry Mechanism: For transient failures caused by temporary network jitter, implement limited retries with interval backoff. Retry 3 times using incremental backoff strategy of 1s, 2s, 3s, while avoiding retry storms and prohibiting infinite retries.
  4. Long Connection Heartbeat Keep-Alive: For WebSocket long connection quote push, send heartbeat packets regularly to detect connection validity. Automatically trigger reconnection logic after disconnection to ensure continuous availability of long connection links.

3.3 Cache Architecture High-Availability Design

Real-time stock quotes have extremely high requirements for query latency. Relying solely on remote API interfaces cannot meet high-concurrency query requirements. We adopt a local cache + Redis cluster dual-layer cache architecture:

  1. Local Memory Cache (Caffeine): Caches hot stock real-time quotes with millisecond-level response, avoiding Redis network overhead and adapting to high-frequency query scenarios;
  2. Redis Cluster Cache: Ensures data consistency across distributed nodes, caches full market quote data with short-term expiration times and automatic refresh to avoid cache data expiration;
  3. Cache Degradation: When Redis cluster fails, automatically degrades to use local cache as fallback, ensuring uninterrupted front-end quote display and achieving cache layer high availability.

3.4 Circuit Breaking, Degradation, and Rate Limiting Strategy

Implement refined traffic control based on Sentinel, adapting to the unstable characteristics of global market interfaces:

  1. Circuit Breaking Strategy: When data source interface failure rate exceeds 20% or timeout count exceeds 50 times within 1 minute, automatically trigger circuit breaking with a duration of 30s. During circuit breaking, reject invalid requests and gradually probe recovery in half-open state;
  2. Degradation Strategy: After interface circuit breaking or timeout, do not directly throw exceptions. Return the latest cached quote data as fallback to ensure business availability;
  3. Rate Limiting Strategy: Limit single-node request QPS according to data source quota restrictions to avoid triggering platform rate limiting and IP bans.

4. Full-Link Exception Classification and Standardized Handling

Global stock real-time quote integration involves long chains and complex exception scenarios. We must eliminate issues such as exception swallowing, missing logs, and imperceptible failures. This article classifies exceptions into four categories: network exceptions, data exceptions, business exceptions, and system exceptions, achieving full-scenario coverage and standardized handling.

4.1 Network Layer Exception Handling

Includes scenarios such as connection timeout, read timeout, connection disconnection, IP ban, and cross-domain network jitter.

Handling Standards: Execute backoff retry for transient exceptions; mark data source as unhealthy after retry failure and automatically switch to backup data source; all network exceptions must record complete stack logs, request parameters, exception time, and target data source information for problem tracing. Prohibit silent swallowing behavior where exceptions are caught without logging, handling, or retrying.

4.2 Data Layer Exception Handling

Third-party market APIs often return dirty data, empty data, malformed formats, price anomalies, expired timestamps, and missing data. Direct passthrough would cause front-end display errors and quantitative strategy failures.

Handling Standards: Establish multi-level data validation rules. Validate core fields including stock code, latest price, price change percentage, trading volume, and timestamp. Directly discard expired data, negative prices, and out-of-range values. Single data exceptions should not affect batch data processing; record exception logs separately and isolate them to avoid single dirty data causing overall task failure.

4.3 Business Layer Exception Handling

Includes business exceptions such as invalid stock codes, market closure, expired interface permissions, and illegal request parameters.

Handling Standards: Distinguish between recoverable and non-recoverable exceptions. Non-recoverable exceptions such as expired permissions and parameter errors should directly terminate requests and trigger alerts; scenarios like market closure and invalid codes should return friendly prompts without triggering retries to reduce invalid request overhead.

4.4 System Layer Exception Handling

Includes internal system exceptions such as thread pool exhaustion, memory overflow, cache breakdown, and MQ message accumulation.

Handling Standards: Isolate collection, processing, and distribution tasks through thread pools to avoid mutual blocking; configure memory threshold alerts and message accumulation alerts; trigger service alert notifications when exceptions occur for timely troubleshooting and handling, ensuring system resources are not exhausted.

5. Java Core Code Practical Implementation

5.1 REST Quote Request Utility Class with Retry, Timeout, and Exception Handling

Integrates connection pool, timeout control, backoff retry, and exception logging to achieve stable cross-border market data requests:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.ZoneId;
import java.time.LocalDateTime;

@Component
public class ITickStockQuoteClient {
    private static final Logger log = LoggerFactory.getLogger(ITickStockQuoteClient.class);
    private static final String API_BASE_URL = "https://api.itick.org/stock";
    private static final String TOKEN = "your_itick_api_key"; // Apply for free at https://itick.org
    private static final int TIME_OUT_MS = 500;
    private static final ObjectMapper MAPPER = new ObjectMapper();

    private static final PoolingHttpClientConnectionManager CONNECTION_MANAGER = new PoolingHttpClientConnectionManager();
    static {
        CONNECTION_MANAGER.setMaxTotal(300);
        CONNECTION_MANAGER.setDefaultMaxPerRoute(80);
    }

    private CloseableHttpClient getHttpClient() {
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(TIME_OUT_MS)
                .setSocketTimeout(TIME_OUT_MS)
                .setConnectionRequestTimeout(TIME_OUT_MS)
                .build();
        return HttpClients.custom()
                .setConnectionManager(CONNECTION_MANAGER)
                .setDefaultRequestConfig(config)
                .build();
    }

    /**
     * Get global stock real-time quotes (with backoff retry)
     * @param region Market region: HK/US/CN
     * @param code   Stock code, e.g., 700.HK / AAPL.US
     */
    @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
    public ITickQuote getRealtimeQuote(String region, String code) throws Exception {
        String url = API_BASE_URL + "/quote?region=" + region + "&code=" + code;
        try (CloseableHttpClient client = getHttpClient()) {
            HttpGet request = new HttpGet(url);
            request.setHeader("accept", "application/json");
            request.setHeader("token", TOKEN);

            String response = client.execute(request, httpResponse -> {
                int statusCode = httpResponse.getStatusLine().getStatusCode();
                if (statusCode != 200) {
                    log.error("iTick market API request failed, region:{} code:{} status:{}", region, code, statusCode);
                    return null;
                }
                return EntityUtils.toString(httpResponse.getEntity());
            });

            if (response == null) {
                throw new RuntimeException("iTick returned empty response");
            }

            return parseITickResponse(response, region, code);
        } catch (Exception e) {
            log.error("iTick quote fetch failed, region:{} code:{} err:{}", region, code, e.getMessage(), e);
            throw new RuntimeException("iTick request exception", e);
        }
    }

    private ITickQuote parseITickResponse(String json, String region, String code) throws Exception {
        JsonNode data = MAPPER.readTree(json).get("data");
        if (data == null) {
            log.warn("iTick response data is empty, region:{} code:{}", region, code);
            return null;
        }
        ITickQuote quote = new ITickQuote();
        quote.setStockCode(region + ":" + code);
        if (data.has("ld")) {
            quote.setPrice(data.get("ld").decimalValue());
        }
        if (data.has("v")) {
            quote.setVolume(data.get("v").longValue());
        }
        if (data.has("t")) {
            quote.setQuoteTime(Instant.ofEpochSecond(data.get("t").longValue())
                    .atZone(ZoneId.systemDefault()).toLocalDateTime());
        }
        return quote;
    }
}
Enter fullscreen mode Exit fullscreen mode

5.2 Core Logic for Data Processing and Exception Filtering

Implements quote data validation, dirty data filtering, and data standardization to prevent abnormal data penetration:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Service
public class ITickDataValidator {
    private static final Logger log = LoggerFactory.getLogger(ITickDataValidator.class);

    /**
     * Validate and process quote data returned by iTick
     */
    public boolean validateAndCleanQuote(ITickQuote quote) {
        if (quote == null || quote.getStockCode() == null || quote.getStockCode().trim().isEmpty()) {
            log.warn("iTick quote: stock code missing, data discarded");
            return false;
        }

        if (quote.getPrice() == null || quote.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
            log.error("iTick quote {}: invalid price price={}, data discarded", quote.getStockCode(), quote.getPrice());
            return false;
        }

        if (quote.getVolume() < 0) {
            log.error("iTick quote {}: abnormal volume volume={}, data discarded", quote.getStockCode(), quote.getVolume());
            return false;
        }

        if (quote.getQuoteTime() != null &&
            quote.getQuoteTime().isBefore(LocalDateTime.now().minusSeconds(5))) {
            log.warn("iTick quote {}: data expired time={}, discarded", quote.getStockCode(), quote.getQuoteTime());
            return false;
        }

        quote.setPrice(quote.getPrice().setScale(2, BigDecimal.ROUND_HALF_UP));
        return true;
    }
}

// Quote entity class
class ITickQuote {
    private String stockCode;
    private BigDecimal price;
    private long volume;
    private LocalDateTime quoteTime;
    // getters / setters omitted
}
Enter fullscreen mode Exit fullscreen mode

5.3 WebSocket Real-Time Quote Push High-Availability Integration

iTick also provides WebSocket long connection push, supporting ultra-low latency real-time quote streams. The following implements authentication, subscription, heartbeat keep-alive, and automatic reconnection based on Java standard WebSocket API:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@ClientEndpoint
public class ITickWebSocketClient {
    private static final Logger log = LoggerFactory.getLogger(ITickWebSocketClient.class);
    private static final String WS_URL = "wss://api.itick.org/stock";   // Paid version
    // private static final String WS_URL_FREE = "wss://api-free.itick.org/stock"; // Free version
    private static final String API_KEY = "your_itick_api_key";

    private Session session;
    private final ScheduledExecutorService heartBeatScheduler = Executors.newSingleThreadScheduledExecutor();
    private volatile boolean connected = false;

    public void connect() throws URISyntaxException, IOException, DeploymentException {
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        container.setDefaultMaxSessionIdleTimeout(30000);
        this.session = container.connectToServer(this, new URI(WS_URL));
    }

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        this.connected = true;
        log.info("iTick WebSocket connected, URL: {}", WS_URL);

        // 1. Send authentication message
        String authMsg = "{\"ac\":\"auth\", \"params\":\"" + API_KEY + "\"}";
        sendMessage(authMsg);
        log.info("iTick authentication request sent");

        // 2. Start heartbeat keep-alive (ping every 20 seconds)
        startHeartBeat();
    }

    /**
     * Subscribe to real-time quotes for specified stocks/products
     * @param params Product codes, e.g., "700$HK,AAPL$US,TSLA$US"
     * @param types  Data types, e.g., "quote" / "depth" / "tick" / "kline"
     */
    public void subscribe(String params, String types) {
        String subMsg = String.format("{\"ac\":\"subscribe\", \"params\":\"%s\", \"types\":\"%s\"}", params, types);
        sendMessage(subMsg);
        log.info("Subscription request sent: params={}, types={}", params, types);
    }

    public void unsubscribe(String params, String types) {
        String unsubMsg = String.format("{\"ac\":\"unsubscribe\", \"params\":\"%s\", \"types\":\"%s\"}", params, types);
        sendMessage(unsubMsg);
        log.info("Unsubscribe: params={}", params);
    }

    @OnMessage
    public void onMessage(String message) {
        log.debug("Received iTick push: {}", message);
        processITickMessage(message);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("iTick WebSocket error occurred", error);
        connected = false;
        reconnectWithBackoff();
    }

    @OnClose
    public void onClose(CloseReason reason) {
        log.warn("iTick WebSocket connection closed, reason: {}", reason.getReasonPhrase());
        connected = false;
        heartBeatScheduler.shutdown();
        if (reason.getCloseCode() != CloseReason.CloseCodes.NORMAL_CLOSURE) {
            reconnectWithBackoff();
        }
    }

    private void sendMessage(String msg) {
        if (session != null && session.isOpen()) {
            try {
                session.getBasicRemote().sendText(msg);
            } catch (IOException e) {
                log.error("Failed to send iTick message", e);
            }
        } else {
            log.warn("Invalid session, message discarded: {}", msg);
        }
    }

    private void startHeartBeat() {
        heartBeatScheduler.scheduleAtFixedRate(() -> {
            if (session != null && session.isOpen()) {
                try {
                    session.getBasicRemote().sendText("{\"ac\":\"ping\"}");
                    log.debug("iTick heartbeat sent");
                } catch (IOException e) {
                    log.error("Failed to send iTick heartbeat", e);
                }
            }
        }, 20, 20, TimeUnit.SECONDS);
    }

    private void reconnectWithBackoff() {
        long delay = 2L;
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(delay * 1000);
                log.info("Attempting {}th reconnection to iTick WebSocket...", i + 1);
                connect();
                if (connected) {
                    log.info("Reconnection successful, resubscribing to historical holdings");
                    resubscribeAll();
                    return;
                }
                delay = Math.min(delay * 2, 128);
            } catch (Exception e) {
                log.error("Reconnection failed", e);
            }
        }
        log.error("iTick WebSocket reconnection exceeded maximum attempts, giving up reconnection, triggering alert");
    }

    private void processITickMessage(String rawMessage) {
        // Parse JSON -> Standardize quote entity -> Validate and filter -> Write to Redis/LocalCache -> Distribute to business parties
    }

    private void resubscribeAll() {
        // Resubscribe to previously subscribed instruments to ensure uninterrupted data
    }
}
Enter fullscreen mode Exit fullscreen mode

5.4 Circuit Breaking and Degradation Fallback Implementation

Implement interface circuit breaking based on Sentinel, returning cached fallback data during failures to ensure business continuity:

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class ITickQuoteGateway {

    @Autowired
    private ITickStockQuoteClient itickClient;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @SentinelResource(value = "getITickQuote", blockHandler = "quoteFallback")
    public ITickQuote getQuoteWithCircuitBreaker(String region, String code) {
        try {
            return itickClient.getRealtimeQuote(region, code);
        } catch (Exception e) {
            throw new RuntimeException("iTick call exception", e);
        }
    }

    public ITickQuote quoteFallback(String region, String code, BlockException blockEx) {
        log.warn("iTick interface triggered circuit breaking degradation, region:{} code:{}, returning cached data", region, code);
        String cacheKey = "itick:quote:" + region + ":" + code;
        String cachedJson = redisTemplate.opsForValue().get(cacheKey);
        if (cachedJson != null) {
            return ITickQuote.fromJson(cachedJson);
        }
        log.error("iTick circuit breaking and cache is empty, business degraded to empty quote");
        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Performance Optimization and Production Implementation Experience

6.1 Asynchronous Batch Processing Optimization

Processing data one by one in a loop is extremely inefficient. For global multi-stock quote batch collection scenarios, we adopt the pattern of thread pool asynchronous batch fetching, batch processing, and batch cache updates to significantly improve throughput. Meanwhile, isolate market quote tasks from different markets through thread pools to avoid single market exceptions affecting global data processing.

6.2 Protocol and Transmission Optimization

For cross-border data transmission, prioritize using HTTP/2 and WebSocket protocols instead of traditional HTTP/1.1 to support multiplexing and reduce connection establishment overhead; enable GZIP compression to reduce cross-border transmission bandwidth consumption and shorten data transmission latency.

6.3 Failure Monitoring and Alerting

Integrate Prometheus + Grafana monitoring system to collect core metrics such as interface success rate, timeout rate, circuit breaking count, data loss rate, and message accumulation volume; configure SMS, email, and DingTalk alerts for exception scenarios to achieve second-level failure detection and rapid positioning.

7. Conclusion

The core difficulty of Java integration with global stock real-time quotes lies not in basic data requests and parsing, but in full-link fault tolerance and architectural fallback under complex network environments, unstable third-party data sources, and high real-time high-availability requirements.

This article provides a complete set of solutions including layered high-availability architecture, multi-data-source redundancy, network fault tolerance, cache fallback, circuit breaking degradation, and full-scenario exception handling. It offers complete code examples from REST to WebSocket, from data processing to circuit breaking degradation, solving the stability challenges of global stock quote integration. The core implementation philosophy can be summarized in three points:

  1. Architecture Anti-Collapse: Stateless clusters, layered decoupling, multi-redundancy fallback, eliminating single points of failure;
  2. Controllable Exceptions: Full-scenario exception classification handling, no swallowing, no blocking, no avalanche;
  3. Controllable Performance: Asynchronous decoupling, batch processing, protocol optimization, ensuring low latency and high concurrency.

This solution has been implemented in production quantitative market systems, stably supporting 7×24 hour global multi-market stock real-time quote access, significantly reducing online failure probability, and providing directly reusable practical references for financial real-time data system development.

Reference Documentation: https://docs.itick.org/websocket/stocks
GitHub: https://github.com/itick-org/

Top comments (0)