One thread sets a proxy property, makes a request, and restores the old value. Another thread does the same with a different proxy. The code looks tidy when read from top to bottom, but both threads are changing shared JVM state.

System.setProperty() changes a system property for the process. It does not attach a route to the thread that called it. A concurrent request can observe a value written for somebody else's work, and an existing client may already have captured configuration of its own.

Make the route part of the client

For Java's HTTP client, create clients with deliberate proxy selectors. Keep separate clients when workloads require distinct routing policies:

HttpClient client = HttpClient.newBuilder()
    .proxy(ProxySelector.of(
        new InetSocketAddress("203.0.113.10", 8080)))
    .connectTimeout(Duration.ofSeconds(5))
    .build();

The imports are java.net, java.net.http, and java.time. The address is illustrative; for permitted public-route tests, the HTTP proxy list supplies candidate endpoints. Configure request deadlines and proxy authentication separately for the application's needs.

The builder's proxy method records a client-level choice. That makes the policy easier to inspect than a sequence of global writes wrapped around asynchronous operations.

Global settings still have a use

A single JVM-wide proxy policy can be appropriate when every participating client is intended to follow it. Set that policy at application startup and verify which libraries honor it.

Problems arise when global properties are used as a temporary switch for individual requests. In a server, background workers and library calls can run concurrently even if your own method appears synchronous. Keep routing state with the client or connector that owns the connection, so another request cannot change it halfway through the job.

Related articles