Repository: InfluxCommunity/influxdb3-java
Pull request #230: feat: support proxy and ssl
Author: login: NguyenHoangSon96
Created: 2025-03-11T11:59:51Z | Merged: 2025-03-27T10:15:52Z

Closes #

## Proposed Changes

Add proxy support for query api

<img width="1552" alt="Screenshot 2025-03-05 at 10 41 55" src="https://github.com/user-attachments/assets/0ff715c9-e87a-4f18-90be-816bff0d6e11" />

## Checklist

<!-- Checkboxes below this note can be erased if not applicable to your Pull Request. -->

- [x] CHANGELOG.md updated
- [x] Rebased/mergeable
- [x] A test has been added if appropriate
- [x] Tests pass
- [x] Commit messages are [conventional](https://www.conventionalcommits.org/en/v1.0.0/)
- [x] Sign [CLA](https://www.influxdata.com/legal/cla/) (if not already signed)



Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48211487..8972ec3c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 ## 1.1.0 [unreleased]
 
+### Features
+
+1. [#229](https://github.com/InfluxCommunity/influxdb3-java/pull/229): Support proxy and custom ssl root certificates
+
 ## 1.0.0 [2024-12-11]
 
 ### Features
diff --git a/examples/docker-compose.yml b/examples/docker-compose.yml
new file mode 100644
index 00000000..b820d1bb
--- /dev/null
+++ b/examples/docker-compose.yml
@@ -0,0 +1,34 @@
+#
+# The MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+version: "3"
+
+services:
+  envoy:
+    image: envoyproxy/envoy:v1.26-latest
+    volumes:
+      - ./envoy.yaml:/etc/envoy/envoy.yaml
+    ports:
+      - "10000:10000"
+    environment:
+      - ENVOY_UID=0
+
diff --git a/examples/envoy.yaml b/examples/envoy.yaml
new file mode 100644
index 00000000..ab539c73
--- /dev/null
+++ b/examples/envoy.yaml
@@ -0,0 +1,84 @@
+#
+# The MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+static_resources:
+  listeners:
+    - name: listener_0
+      address:
+        socket_address: { address: 0.0.0.0, port_value: 10000 }
+      filter_chains:
+        - filter_chain_match:
+          filters:
+            - name: envoy.filters.network.http_connection_manager
+              typed_config:
+                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+                stat_prefix: ingress_http
+                access_log:
+                  - name: envoy.access_loggers.stdout
+                    typed_config:
+                      "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
+                http2_protocol_options:
+                  allow_connect: true
+                upgrade_configs:
+                  - upgrade_type: CONNECT
+                http_filters:
+                  - name: envoy.filters.http.router
+                    typed_config:
+                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+
+                route_config:
+                  name: local_route
+                  virtual_hosts:
+                    - name: local_service
+                      domains: [ "*" ]
+                      routes:
+                        - match:
+                            connect_matcher: { }
+                          route:
+                            cluster: influxdb_cluster
+                            upgrade_configs:
+                              upgrade_type: CONNECT
+                              connect_config: { }
+                        - match:
+                            prefix: "/"
+                          route:
+                            cluster: influxdb_cluster
+                            prefix_rewrite: "/"
+                            auto_host_rewrite: true
+                            timeout: 10s
+                      cors:
+                        allow_origin_string_match:
+                          - prefix: "*"
+                        allow_methods: GET, PUT, DELETE, POST, OPTIONS
+  clusters:
+    - name: influxdb_cluster
+      connect_timeout: 10s
+      type: STRICT_DNS
+      load_assignment:
+        cluster_name: influxdb_cluster
+        endpoints:
+          - lb_endpoints:
+              - endpoint:
+                  address:
+                    socket_address:
+                      address: "us-east-1-1.aws.cloud2.influxdata.com"
+                      port_value: 443
\ No newline at end of file
diff --git a/examples/src/main/java/com/influxdb/v3/ProxyExample.java b/examples/src/main/java/com/influxdb/v3/ProxyExample.java
new file mode 100644
index 00000000..76a3204d
--- /dev/null
+++ b/examples/src/main/java/com/influxdb/v3/ProxyExample.java
@@ -0,0 +1,72 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() {
+    }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyUrl = "http://localhost:10000";
+        String sslRootsFilePath = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("INFLUXDB_URL"))
+                .token(System.getenv("INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("INFLUXDB_DATABASE"))
+                .proxyUrl(proxyUrl)
+                .sslRootsFilePath(sslRootsFilePath)
+                .build();
+
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        String testId = UUID.randomUUID().toString();
+        Point point = Point.measurement("My_Home")
+                .setTag("room", "Kitchen")
+                .setField("temp", 12.7)
+                .setField("hum", 37)
+                .setField("testId", testId);
+        influxDBClient.writePoint(point);
+
+        String query = String.format("SELECT * FROM \"My_Home\" WHERE \"testId\" = '%s'", testId);
+        try (Stream<PointValues> stream = influxDBClient.queryPoints(query)) {
+            stream.findFirst().ifPresent(values -> {
+                assert values.getTimestamp() != null;
+                System.out.printf("room[%s]: %s, temp: %3.2f, hum: %d",
+                        new java.util.Date(values.getTimestamp().longValue() / 1000000),
+                        values.getTag("room"),
+                        (Double) values.getField("temp"),
+                        (Long) values.getField("hum"));
+            });
+        }
+    }
+}
+
diff --git a/pom.xml b/pom.xml
index cc9af3c1..f4cfc172 100644
--- a/pom.xml
+++ b/pom.xml
@@ -399,7 +399,7 @@
                                 **/target/**, **/*.jar, **/.git/**, **/.*, **/*.png, **/*.iml, **/*.bolt, .idea/**,
                                 **/*nightly*/**, **/.m2/**, LICENSE, **/*.md, **/.github/**, license_header.txt,
                                 release.properties/, **/pom.xml.releaseBackup, **/pom.xml.tag, **/semantic.yml,
-                                .circleci/config.yml
+                                .circleci/config.yml, **/*.pem
                             </excludes>
                         </licenseSet>
                     </licenseSets>
diff --git a/src/main/java/com/influxdb/v3/client/config/ClientConfig.java b/src/main/java/com/influxdb/v3/client/config/ClientConfig.java
index 55508a01..fc248e30 100644
--- a/src/main/java/com/influxdb/v3/client/config/ClientConfig.java
+++ b/src/main/java/com/influxdb/v3/client/config/ClientConfig.java
@@ -57,20 +57,23 @@
  *     <li><code>disableServerCertificateValidation</code> -
  *          disable server certificate validation for HTTPS connections
  *     </li>
- *     <li><code>proxy</code> - HTTP proxy selector</li>
+ *     <li><code>proxyUrl</code> - Proxy url for query api and write api</li>
  *     <li><code>authenticator</code> - HTTP proxy authenticator</li>
  *     <li><code>headers</code> - headers to be added to requests</li>
+ *     <li><code>sslRootsFilePath</code> - Path to the stored certificates file in PEM format</li>
  * </ul>
  * <p>
  * If you want to create a client with custom configuration, you can use following code:
  * <pre>
  * ClientConfig config = new ClientConfig.Builder()
- *     .host("https://us-east-1-1.aws.cloud2.influxdata.com")
+ *     .host("<a href="https://us-east-1-1.aws.cloud2.influxdata.com">
+ *         https://us-east-1-1.aws.cloud2.influxdata.com
+ *         </a>")
  *     .token("my-token".toCharArray())
  *     .database("my-database")
  *     .writePrecision(WritePrecision.S)
  *     .gzipThreshold(4096)
- *     .proxy(ProxySelector.of(new InetSocketAddress("proxy.local", 8888)))
+ *     .proxyUrl("http://localhost:10000")
  *     .build();
  *
  * try (InfluxDBClient client = InfluxDBClient.getInstance(config)) {
@@ -84,7 +87,6 @@
  * Immutable class.
  */
 public final class ClientConfig {
-
     private final String host;
     private final char[] token;
     private final String authScheme;
@@ -96,9 +98,16 @@ public final class ClientConfig {
     private final Duration timeout;
     private final Boolean allowHttpRedirects;
     private final Boolean disableServerCertificateValidation;
-    private final ProxySelector proxy;
+    private final String proxyUrl;
     private final Authenticator authenticator;
     private final Map<String, String> headers;
+    private final String sslRootsFilePath;
+
+    /**
+     * Deprecated use {@link #proxyUrl}.
+     */
+    @Deprecated
+    private final ProxySelector proxy;
 
     /**
      * Gets URL of the InfluxDB server.
@@ -213,12 +222,34 @@ public Boolean getDisableServerCertificateValidation() {
      * Gets the proxy.
      *
      * @return the proxy, may be null
+     * Deprecated use {@link #proxyUrl}
      */
     @Nullable
+    @Deprecated
     public ProxySelector getProxy() {
         return proxy;
     }
 
+    /**
+     * Gets the proxy url.
+     *
+     * @return the proxy url, may be null
+     */
+    @Nullable
+    public String getProxyUrl() {
+        return proxyUrl;
+    }
+
+    /**
+     * Gets certificates file path.
+     *
+     * @return the certificates file path, may be null
+     */
+    @Nullable
+    public String sslRootsFilePath() {
+        return sslRootsFilePath;
+    }
+
     /**
      * Gets the (proxy) authenticator.
      *
@@ -269,17 +300,19 @@ public boolean equals(final Object o) {
                 && Objects.equals(allowHttpRedirects, that.allowHttpRedirects)
                 && Objects.equals(disableServerCertificateValidation, that.disableServerCertificateValidation)
                 && Objects.equals(proxy, that.proxy)
+                && Objects.equals(proxyUrl, that.proxyUrl)
                 && Objects.equals(authenticator, that.authenticator)
-                && Objects.equals(headers, that.headers);
+                && Objects.equals(headers, that.headers)
+                && Objects.equals(sslRootsFilePath, that.sslRootsFilePath);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(host, Arrays.hashCode(token), authScheme, organization,
-          database, writePrecision, gzipThreshold,
-          timeout, allowHttpRedirects, disableServerCertificateValidation,
-          proxy, authenticator, headers,
-          defaultTags);
+                database, writePrecision, gzipThreshold,
+                timeout, allowHttpRedirects, disableServerCertificateValidation,
+                proxy, proxyUrl, authenticator, headers,
+                defaultTags, sslRootsFilePath);
     }
 
     @Override
@@ -294,9 +327,11 @@ public String toString() {
                 .add("allowHttpRedirects=" + allowHttpRedirects)
                 .add("disableServerCertificateValidation=" + disableServerCertificateValidation)
                 .add("proxy=" + proxy)
+                .add("proxyUrl=" + proxyUrl)
                 .add("authenticator=" + authenticator)
                 .add("headers=" + headers)
                 .add("defaultTags=" + defaultTags)
+                .add("sslRootsFilePath=" + sslRootsFilePath)
                 .toString();
     }
 
@@ -318,8 +353,10 @@ public static final class Builder {
         private Boolean allowHttpRedirects;
         private Boolean disableServerCertificateValidation;
         private ProxySelector proxy;
+        private String proxyUrl;
         private Authenticator authenticator;
         private Map<String, String> headers;
+        private String sslRootsFilePath;
 
         /**
          * Sets the URL of the InfluxDB server.
@@ -472,6 +509,7 @@ public Builder disableServerCertificateValidation(@Nullable final Boolean disabl
          *
          * @param proxy Proxy selector.
          * @return this
+         * Deprecated use {@link #proxyUrl}
          */
         @Nonnull
         public Builder proxy(@Nullable final ProxySelector proxy) {
@@ -480,6 +518,19 @@ public Builder proxy(@Nullable final ProxySelector proxy) {
             return this;
         }
 
+        /**
+         * Sets the proxy url. Default is 'null'.
+         *
+         * @param proxyUrl Proxy url.
+         * @return this
+         */
+        @Nonnull
+        public Builder proxyUrl(@Nullable final String proxyUrl) {
+
+            this.proxyUrl = proxyUrl;
+            return this;
+        }
+
         /**
          * Sets the proxy authenticator. Default is 'null'.
          *
@@ -498,7 +549,9 @@ public Builder authenticator(@Nullable final Authenticator authenticator) {
          * such as tracing headers. To add custom headers use following code:
          * <pre>
          * ClientConfig config = new ClientConfig.Builder()
-         *     .host("https://us-east-1-1.aws.cloud2.influxdata.com")
+         *     .host("<a href="https://us-east-1-1.aws.cloud2.influxdata.com">
+         *         https://us-east-1-1.aws.cloud2.influxdata.com
+         *         </a>")
          *     .token("my-token".toCharArray())
          *     .database("my-database")
          *     .headers(Map.of("X-Tracing-Id", "123"))
@@ -523,6 +576,19 @@ public Builder headers(@Nullable final Map<String, String> headers) {
             return this;
         }
 
+        /**
+         * Sets certificate file path. Default is 'null'.
+         *
+         * @param sslRootsFilePath The certificate file path
+         * @return this
+         */
+        @Nonnull
+        public Builder sslRootsFilePath(@Nullable final String sslRootsFilePath) {
+
+            this.sslRootsFilePath = sslRootsFilePath;
+            return this;
+        }
+
         /**
          * Build an instance of {@code ClientConfig}.
          *
@@ -658,7 +724,9 @@ private ClientConfig(@Nonnull final Builder builder) {
         disableServerCertificateValidation = builder.disableServerCertificateValidation != null
                 ? builder.disableServerCertificateValidation : false;
         proxy = builder.proxy;
+        proxyUrl = builder.proxyUrl;
         authenticator = builder.authenticator;
         headers = builder.headers;
+        sslRootsFilePath = builder.sslRootsFilePath;
     }
 }
diff --git a/src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java b/src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java
index 5280550c..e1b37298 100644
--- a/src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java
+++ b/src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java
@@ -21,6 +21,8 @@
  */
 package com.influxdb.v3.client.internal;
 
+import java.io.FileInputStream;
+import java.net.InetSocketAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
@@ -39,22 +41,35 @@
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import io.grpc.HttpConnectProxiedSocketAddress;
 import io.grpc.Metadata;
+import io.grpc.ProxyDetector;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NettyChannelBuilder;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
 import org.apache.arrow.flight.FlightClient;
+import org.apache.arrow.flight.FlightGrpcUtils;
 import org.apache.arrow.flight.FlightStream;
 import org.apache.arrow.flight.HeaderCallOption;
 import org.apache.arrow.flight.Location;
+import org.apache.arrow.flight.LocationSchemes;
 import org.apache.arrow.flight.Ticket;
 import org.apache.arrow.flight.grpc.MetadataAdapter;
 import org.apache.arrow.memory.RootAllocator;
 import org.apache.arrow.util.AutoCloseables;
 import org.apache.arrow.vector.VectorSchemaRoot;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.influxdb.v3.client.config.ClientConfig;
 import com.influxdb.v3.client.query.QueryType;
 
 final class FlightSqlClient implements AutoCloseable {
 
+    private static final Logger LOG = LoggerFactory.getLogger(FlightSqlClient.class);
+
     private final FlightClient client;
 
     private final Map<String, String> defaultHeaders = new HashMap<>();
@@ -83,8 +98,6 @@ final class FlightSqlClient implements AutoCloseable {
             defaultHeaders.putAll(config.getHeaders());
         }
 
-        Package mainPackage = RestClient.class.getPackage();
-
         this.client = (client != null) ? client : createFlightClient(config);
     }
 
@@ -130,11 +143,49 @@ public void close() throws Exception {
     private FlightClient createFlightClient(@Nonnull final ClientConfig config) {
         Location location = createLocation(config);
 
-        return FlightClient.builder()
-                .location(location)
-                .allocator(new RootAllocator(Long.MAX_VALUE))
-                .verifyServer(!config.getDisableServerCertificateValidation())
-                .build();
+        final NettyChannelBuilder nettyChannelBuilder = NettyChannelBuilder.forTarget(location.getUri().getHost());
+
+        if (LocationSchemes.GRPC_TLS.equals(location.getUri().getScheme())) {
+            nettyChannelBuilder.useTransportSecurity();
+
+            SslContext nettySslContext = createNettySslContext(config);
+            nettyChannelBuilder.sslContext(nettySslContext);
+        } else {
+            nettyChannelBuilder.usePlaintext();
+        }
+
+        if (config.getProxyUrl() != null) {
+            ProxyDetector proxyDetector = createProxyDetector(config.getHost(), config.getProxyUrl());
+            nettyChannelBuilder.proxyDetector(proxyDetector);
+        }
+
+        if (config.getProxy() != null) {
+            LOG.warn("proxy property in ClientConfig will not work in query api, use proxyUrl property instead");
+        }
+
+        nettyChannelBuilder.maxTraceEvents(0)
+                .maxInboundMessageSize(Integer.MAX_VALUE)
+                .maxInboundMetadataSize(Integer.MAX_VALUE);
+
+        return FlightGrpcUtils.createFlightClient(new RootAllocator(Long.MAX_VALUE), nettyChannelBuilder.build());
+    }
+
+    @Nonnull
+    SslContext createNettySslContext(@Nonnull final ClientConfig config) {
+        try {
+            SslContextBuilder sslContextBuilder;
+            sslContextBuilder = GrpcSslContexts.forClient();
+            if (config.getDisableServerCertificateValidation()) {
+                sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
+            } else if (config.sslRootsFilePath() != null) {
+                try (FileInputStream fileInputStream = new FileInputStream(config.sslRootsFilePath())) {
+                    sslContextBuilder.trustManager(fileInputStream);
+                }
+            }
+            return sslContextBuilder.build();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
     }
 
     @Nonnull
@@ -166,6 +217,22 @@ private HeaderCallOption metadataHeader(@Nonnull final Map<String, String> reque
         return new HeaderCallOption(metadata);
     }
 
+    ProxyDetector createProxyDetector(@Nonnull final String targetUrl, @Nonnull final String proxyUrl) {
+        URI targetUri = URI.create(targetUrl);
+        URI proxyUri = URI.create(proxyUrl);
+        return (targetServerAddress) -> {
+            InetSocketAddress targetAddress = (InetSocketAddress) targetServerAddress;
+            if (targetUri.getHost().equals(targetAddress.getHostString())
+                    && targetUri.getPort() == targetAddress.getPort()) {
+                return HttpConnectProxiedSocketAddress.newBuilder()
+                        .setProxyAddress(new InetSocketAddress(proxyUri.getHost(), proxyUri.getPort()))
+                        .setTargetAddress(targetAddress)
+                        .build();
+            }
+            return null;
+        };
+    }
+
     private static final class FlightSqlIterator implements Iterator<VectorSchemaRoot>, AutoCloseable {
 
         private final List<AutoCloseable> autoCloseable = new ArrayList<>();
diff --git a/src/main/java/com/influxdb/v3/client/internal/RestClient.java b/src/main/java/com/influxdb/v3/client/internal/RestClient.java
index 89607807..971abd24 100644
--- a/src/main/java/com/influxdb/v3/client/internal/RestClient.java
+++ b/src/main/java/com/influxdb/v3/client/internal/RestClient.java
@@ -21,11 +21,19 @@
  */
 package com.influxdb.v3.client.internal;
 
+import java.io.FileInputStream;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
+import java.security.KeyStore;
 import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Stream;
@@ -33,6 +41,7 @@
 import javax.annotation.Nullable;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
@@ -97,8 +106,14 @@ public void checkServerTrusted(
         // default headers
         this.defaultHeaders = config.getHeaders() != null ? Map.copyOf(config.getHeaders()) : null;
 
-        // proxy
-        if (config.getProxy() != null) {
+        if (config.getProxyUrl() != null) {
+            URI proxyUri = URI.create(config.getProxyUrl());
+            ProxySelector proxy = ProxySelector.of(new InetSocketAddress(proxyUri.getHost(), proxyUri.getPort()));
+            builder.proxy(proxy);
+            if (config.getAuthenticator() != null) {
+                builder.authenticator(config.getAuthenticator());
+            }
+        } else if (config.getProxy() != null) {
             builder.proxy(config.getProxy());
             if (config.getAuthenticator() != null) {
                 builder.authenticator(config.getAuthenticator());
@@ -107,11 +122,16 @@ public void checkServerTrusted(
 
         if (baseUrl.startsWith("https")) {
             try {
+                SSLContext sslContext = SSLContext.getInstance("TLS");
                 if (config.getDisableServerCertificateValidation()) {
-                    SSLContext sslContext = SSLContext.getInstance("TLS");
                     sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom());
-                    builder.sslContext(sslContext);
+                } else if (config.sslRootsFilePath() != null) {
+                    X509TrustManager x509TrustManager = getX509TrustManagerFromFile(config.sslRootsFilePath());
+                    sslContext.init(null, new X509TrustManager[]{x509TrustManager}, new SecureRandom());
+                } else {
+                    sslContext.init(null, null, new SecureRandom());
                 }
+                builder.sslContext(sslContext);
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
@@ -217,6 +237,38 @@ void request(@Nonnull final String path,
         }
     }
 
+    private X509TrustManager getX509TrustManagerFromFile(@Nonnull final String filePath) {
+        try {
+            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+            trustStore.load(null);
+
+            FileInputStream fis = new FileInputStream(filePath);
+            List<? extends Certificate> certificates = new ArrayList<Certificate>(
+                    CertificateFactory.getInstance("X.509")
+                            .generateCertificates(fis)
+            );
+
+            for (int i = 0; i < certificates.size(); i++) {
+                Certificate cert = certificates.get(i);
+                trustStore.setCertificateEntry("alias" + i, cert);
+            }
+
+            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
+                    TrustManagerFactory.getDefaultAlgorithm()
+            );
+            trustManagerFactory.init(trustStore);
+            X509TrustManager x509TrustManager = null;
+            for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
+                if (trustManager instanceof X509TrustManager) {
+                    x509TrustManager = (X509TrustManager) trustManager;
+                }
+            }
+            return x509TrustManager;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     @Override
     public void close() {
     }
diff --git a/src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java b/src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java
index f1e868a2..e1f1667f 100644
--- a/src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java
+++ b/src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java
@@ -21,22 +21,34 @@
  */
 package com.influxdb.v3.client;
 
-import java.math.BigInteger;
-import java.time.Instant;
 import java.util.Map;
 import java.util.Properties;
-import java.util.UUID;
-import java.util.stream.Stream;
 
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
 
-import com.influxdb.v3.client.write.WriteOptions;
-import com.influxdb.v3.client.write.WritePrecision;
+import com.influxdb.v3.client.config.ClientConfig;
 
 public class InfluxDBClientTest {
 
+    @Test
+    void withProxyUrl() {
+        String proxyUrl = "http://localhost:10000";
+        ClientConfig.Builder builder = new ClientConfig.Builder();
+        builder.proxyUrl(proxyUrl);
+        ClientConfig clientConfig = builder.build();
+        Assertions.assertThat(clientConfig.getProxyUrl()).isEqualTo(proxyUrl);
+    }
+
+    @Test
+    void withSslRootsFilePath() {
+        String path = "/path/to/cert";
+        ClientConfig.Builder builder = new ClientConfig.Builder();
+        builder.sslRootsFilePath(path);
+        ClientConfig clientConfig = builder.build();
+        Assertions.assertThat(clientConfig.sslRootsFilePath()).isEqualTo(path);
+    }
+
     @Test
     void requiredHost() {
 
@@ -125,52 +137,4 @@ public void unsupportedQueryParams() throws Exception {
         }
     }
 
-    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_URL", matches = ".*")
-    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_TOKEN", matches = ".*")
-    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_DATABASE", matches = ".*")
-    @Test
-    public void testQuery() throws Exception {
-        try (InfluxDBClient client = InfluxDBClient.getInstance(
-                System.getenv("TESTING_INFLUXDB_URL"),
-                System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray(),
-                System.getenv("TESTING_INFLUXDB_DATABASE"),
-                null)) {
-            String uuid = UUID.randomUUID().toString();
-            long timestamp = Instant.now().getEpochSecond();
-            String record = String.format(
-                    "host10,tag=empty "
-                            + "name=\"intel\","
-                            + "mem_total=2048,"
-                            + "disk_free=100i,"
-                            + "temperature=100.86,"
-                            + "isActive=true,"
-                            + "testId=\"%s\" %d",
-                    uuid,
-                    timestamp
-            );
-            client.writeRecord(record, new WriteOptions(null, WritePrecision.S, null));
-
-            Map<String, Object> parameters = Map.of("testId", uuid);
-            String sql = "Select * from host10 where \"testId\"=$testId";
-            try (Stream<Object[]> stream = client.query(sql, parameters)) {
-                stream.findFirst()
-                      .ifPresent(objects -> {
-                          Assertions.assertThat(objects[0].getClass()).isEqualTo(Long.class);
-                          Assertions.assertThat(objects[0]).isEqualTo(100L);
-
-                          Assertions.assertThat(objects[1].getClass()).isEqualTo(Boolean.class);
-                          Assertions.assertThat(objects[1]).isEqualTo(true);
-
-                          Assertions.assertThat(objects[2].getClass()).isEqualTo(Double.class);
-                          Assertions.assertThat(objects[2]).isEqualTo(2048.0);
-
-                          Assertions.assertThat(objects[3].getClass()).isEqualTo(String.class);
-                          Assertions.assertThat(objects[3]).isEqualTo("intel");
-
-                          Assertions.assertThat(objects[7].getClass()).isEqualTo(BigInteger.class);
-                          Assertions.assertThat(objects[7]).isEqualTo(BigInteger.valueOf(timestamp * 1_000_000_000));
-                      });
-            }
-        }
-    }
 }
diff --git a/src/test/java/com/influxdb/v3/client/integration/E2ETest.java b/src/test/java/com/influxdb/v3/client/integration/E2ETest.java
new file mode 100644
index 00000000..0eff6cdc
--- /dev/null
+++ b/src/test/java/com/influxdb/v3/client/integration/E2ETest.java
@@ -0,0 +1,210 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3.client.integration;
+
+import java.math.BigInteger;
+import java.net.ConnectException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.time.Instant;
+import java.util.Map;
+import java.util.UUID;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+import javax.annotation.Nonnull;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+import com.influxdb.v3.client.write.WriteOptions;
+import com.influxdb.v3.client.write.WritePrecision;
+
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+
+public class E2ETest {
+
+    private static final java.util.logging.Logger LOG = Logger.getLogger(E2ETest.class.getName());
+
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_URL", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_TOKEN", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_DATABASE", matches = ".*")
+    @Test
+    void testQueryWithProxy() {
+        String proxyUrl = "http://localhost:10000";
+
+        try {
+            // Continue to run this test only if Envoy proxy is running on this address http://localhost:10000
+            String url = String.format("http://%s:%d", "localhost", 10000);
+            URLConnection hpCon = new URL(url).openConnection();
+            hpCon.connect();
+        } catch (Exception e) {
+            if (e instanceof ConnectException && e.getMessage().contains("Connection refused")) {
+                LOG.warning("Tests with proxy have been skipped because no proxy is running on " + proxyUrl);
+                assumeFalse(e.getMessage().contains("Connection refused"));
+                return;
+            }
+        }
+
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("TESTING_INFLUXDB_URL"))
+                .token(System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("TESTING_INFLUXDB_DATABASE"))
+                .proxyUrl(proxyUrl)
+                .build();
+
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        influxDBClient.writePoint(
+                Point.measurement("test1")
+                        .setField("field", "field1")
+        );
+
+        try (Stream<PointValues> stream = influxDBClient.queryPoints("SELECT * FROM test1")) {
+            stream.findFirst()
+                    .ifPresent(pointValues -> {
+                        Assertions.assertThat(pointValues.getField("field")).isEqualTo("field1");
+                    });
+        }
+    }
+
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_URL", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_TOKEN", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_DATABASE", matches = ".*")
+    @Test
+    void correctSslCertificates() throws Exception {
+        // This is real certificate downloaded from https://cloud2.influxdata.com
+        String influxDBcertificateFile = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("TESTING_INFLUXDB_URL"))
+                .token(System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("TESTING_INFLUXDB_DATABASE"))
+                .sslRootsFilePath(influxDBcertificateFile)
+                .build();
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        assertGetDataSuccess(influxDBClient);
+    }
+
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_URL", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_TOKEN", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_DATABASE", matches = ".*")
+    @Test
+    void wrongSslCertificate() {
+        String certificateFile = "src/test/java/com/influxdb/v3/client/testdata/docker.com.pem";
+
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("TESTING_INFLUXDB_URL"))
+                .token(System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("TESTING_INFLUXDB_DATABASE"))
+                .sslRootsFilePath(certificateFile)
+                .build();
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        Assertions.assertThatThrownBy(() -> assertGetDataSuccess(influxDBClient))
+                .isInstanceOf(Exception.class);
+    }
+
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_URL", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_TOKEN", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_DATABASE", matches = ".*")
+    @Test
+    void disableServerCertificateValidation() {
+        String wrongCertificateFile = "src/test/java/com/influxdb/v3/client/testdata/docker.com.pem";
+
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("TESTING_INFLUXDB_URL"))
+                .token(System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("TESTING_INFLUXDB_DATABASE"))
+                .disableServerCertificateValidation(true)
+                .sslRootsFilePath(wrongCertificateFile)
+                .build();
+
+        // Test succeeded with wrong certificate file because disableServerCertificateValidation is true
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        assertGetDataSuccess(influxDBClient);
+    }
+
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_URL", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_TOKEN", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_DATABASE", matches = ".*")
+    @Test
+    public void testQuery() throws Exception {
+        try (InfluxDBClient client = InfluxDBClient.getInstance(
+                System.getenv("TESTING_INFLUXDB_URL"),
+                System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray(),
+                System.getenv("TESTING_INFLUXDB_DATABASE"),
+                null)) {
+            String uuid = UUID.randomUUID().toString();
+            long timestamp = Instant.now().getEpochSecond();
+            String record = String.format(
+                    "host10,tag=empty "
+                            + "name=\"intel\","
+                            + "mem_total=2048,"
+                            + "disk_free=100i,"
+                            + "temperature=100.86,"
+                            + "isActive=true,"
+                            + "testId=\"%s\" %d",
+                    uuid,
+                    timestamp
+            );
+            client.writeRecord(record, new WriteOptions(null, WritePrecision.S, null));
+
+            Map<String, Object> parameters = Map.of("testId", uuid);
+            String sql = "Select * from host10 where \"testId\"=$testId";
+            try (Stream<Object[]> stream = client.query(sql, parameters)) {
+                stream.findFirst()
+                        .ifPresent(objects -> {
+                            Assertions.assertThat(objects[0].getClass()).isEqualTo(Long.class);
+                            Assertions.assertThat(objects[0]).isEqualTo(100L);
+
+                            Assertions.assertThat(objects[1].getClass()).isEqualTo(Boolean.class);
+                            Assertions.assertThat(objects[1]).isEqualTo(true);
+
+                            Assertions.assertThat(objects[2].getClass()).isEqualTo(Double.class);
+                            Assertions.assertThat(objects[2]).isEqualTo(2048.0);
+
+                            Assertions.assertThat(objects[3].getClass()).isEqualTo(String.class);
+                            Assertions.assertThat(objects[3]).isEqualTo("intel");
+
+                            Assertions.assertThat(objects[7].getClass()).isEqualTo(BigInteger.class);
+                            Assertions.assertThat(objects[7]).isEqualTo(BigInteger.valueOf(timestamp * 1_000_000_000));
+                        });
+            }
+        }
+    }
+
+    private void assertGetDataSuccess(@Nonnull final InfluxDBClient influxDBClient) {
+        influxDBClient.writePoint(
+                Point.measurement("test1")
+                        .setField("field", "field1")
+        );
+        try (Stream<PointValues> stream = influxDBClient.queryPoints("SELECT * FROM test1")) {
+            stream.findFirst()
+                    .ifPresent(pointValues -> {
+                        Assertions.assertThat(pointValues.getField("field")).isEqualTo("field1");
+                    });
+        }
+    }
+}
diff --git a/src/test/java/com/influxdb/v3/client/internal/FlightSqlClientTest.java b/src/test/java/com/influxdb/v3/client/internal/FlightSqlClientTest.java
index 19e0ea0b..84a609ad 100644
--- a/src/test/java/com/influxdb/v3/client/internal/FlightSqlClientTest.java
+++ b/src/test/java/com/influxdb/v3/client/internal/FlightSqlClientTest.java
@@ -21,9 +21,12 @@
  */
 package com.influxdb.v3.client.internal;
 
+import java.net.InetSocketAddress;
 import java.net.URISyntaxException;
 import java.util.Map;
 
+import io.grpc.HttpConnectProxiedSocketAddress;
+import io.grpc.ProxyDetector;
 import io.grpc.internal.GrpcUtil;
 import org.apache.arrow.flight.CallHeaders;
 import org.apache.arrow.flight.CallInfo;
@@ -83,6 +86,31 @@ void tearDown() throws Exception {
         }
     }
 
+    @Test
+    void flightSqlClient() throws Exception {
+        String correctHost = "grpc+unix://tmp/dummy.sock";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(correctHost)
+                .token("Token".toCharArray())
+                .build();
+        try (FlightSqlClient flightSqlClient = new FlightSqlClient(clientConfig)) {
+            Assertions.assertThat(flightSqlClient).isNotNull();
+        }
+
+        FlightClient.Builder builder = FlightClient.builder(allocator, server.getLocation());
+        try (FlightClient flightClient = builder.build()) {
+            FlightSqlClient flightSqlClient = new FlightSqlClient(clientConfig, flightClient);
+            Assertions.assertThat(flightSqlClient).isNotNull();
+        }
+
+        var inCorrectHost = "grpc+unix://///tmp/dummy.sock";
+        ClientConfig clientConfig1 = new ClientConfig.Builder()
+                .host(inCorrectHost)
+                .token("Token".toCharArray())
+                .build();
+        Assertions.assertThatThrownBy(() -> new FlightSqlClient(clientConfig1));
+    }
+
     @Test
     public void invalidHost() {
         ClientConfig clientConfig = new ClientConfig.Builder()
@@ -272,6 +300,31 @@ public void useParamsFromQueryConfig() throws Exception {
         }
     }
 
+    @Test
+    void createProxyDetector() {
+        String targetUrl = "https://localhost:80";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(targetUrl)
+                .build();
+        try (FlightSqlClient flightSqlClient = new FlightSqlClient(clientConfig)) {
+            String proxyUrl = "http://localhost:10000";
+            ProxyDetector proxyDetector = flightSqlClient.createProxyDetector(targetUrl, proxyUrl);
+            Assertions.assertThat(proxyDetector.proxyFor(
+                    new InetSocketAddress("localhost", 80)
+            )).isEqualTo(HttpConnectProxiedSocketAddress.newBuilder()
+                    .setProxyAddress(new InetSocketAddress("localhost", 10000))
+                    .setTargetAddress(new InetSocketAddress("localhost", 80))
+                    .build());
+
+            // Return null case
+            Assertions.assertThat(proxyDetector.proxyFor(
+                    new InetSocketAddress("123.2.3.1", 80)
+            )).isNull();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     static class CallHeadersMiddleware implements FlightClientMiddleware.Factory {
         CallHeaders headers;
 
diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java
index bd53a3ce..47f30acc 100644
--- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java
+++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java
@@ -291,6 +291,24 @@ public void proxy() throws InterruptedException {
         Assertions.assertThat(recordedRequest.getRequestLine()).isEqualTo("GET http://foo.com:8086/ping HTTP/1.1");
     }
 
+    @Test
+    public void proxyUrl() throws InterruptedException {
+        mockServer.enqueue(createResponse(200));
+
+        restClient = new RestClient(new ClientConfig.Builder()
+                .host("http://foo.com:8086")
+                .proxyUrl(String.format("http://%s:%d", mockServer.getHostName(), mockServer.getPort()))
+                .build());
+
+        restClient.request("ping", HttpMethod.GET, null, null, null);
+
+        RecordedRequest recordedRequest = mockServer.takeRequest();
+
+        Assertions.assertThat(recordedRequest.getRequestUrl()).isNotNull();
+        Assertions.assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(baseURL); // server is used as proxy
+        Assertions.assertThat(recordedRequest.getRequestLine()).isEqualTo("GET http://foo.com:8086/ping HTTP/1.1");
+    }
+
     @Test
     public void proxyWithAuthentication() throws InterruptedException {
         mockServer.enqueue(createResponseWithHeaders(407, Map.of("Proxy-Authenticate", "Basic")));
@@ -298,7 +316,7 @@ public void proxyWithAuthentication() throws InterruptedException {
 
         restClient = new RestClient(new ClientConfig.Builder()
                 .host("http://foo.com:8086")
-                .proxy(ProxySelector.of((InetSocketAddress) mockServer.toProxyAddress().address()))
+                .proxyUrl(String.format("http://%s:%d", mockServer.getHostName(), mockServer.getPort()))
                 .authenticator(new Authenticator() {
                     @Override
                     protected PasswordAuthentication getPasswordAuthentication() {
diff --git a/src/test/java/com/influxdb/v3/client/testdata/docker.com.pem b/src/test/java/com/influxdb/v3/client/testdata/docker.com.pem
new file mode 100644
index 00000000..ac9ed44c
--- /dev/null
+++ b/src/test/java/com/influxdb/v3/client/testdata/docker.com.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFxDCCBKygAwIBAgIQDE0Qon/WHglZ2jXwZzD0xjANBgkqhkiG9w0BAQsFADA8
+MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRwwGgYDVQQDExNBbWF6b24g
+UlNBIDIwNDggTTAyMB4XDTI0MDgxODAwMDAwMFoXDTI1MDkxNjIzNTk1OVowFzEV
+MBMGA1UEAwwMKi5kb2NrZXIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAytONEwLLBXfyepD/iE7N76+xGDWE/7g21rpPRejCobpaKGVKSL4Y8Pf9
+whjh5pzBLxcpQMpYcg/oW+Cp4scjhXyi9yqrOC2Vf26DRA3ufjbescZUjPP28mPO
+N1gGQhnr0Sa7mbhNo5JVE7yxLrjhAZFCdEpl1LYdfosYxeBowVOaxBsGfUWCxYuI
+HbolTUUNWJKaAN7knKmrHF0a2a6BftaTyFK/6N1FV3rXs5oD+5DEYVFN8193fdz5
+DUcf5p7xzjx9yXmHfdomznUPL5Sja2FSigH+Gm6EG3cBKylVCpafwQhbYbpdkP12
+p7KtsPGUWIwBAyRr1AUqx0ceIafa9wIDAQABo4IC5TCCAuEwHwYDVR0jBBgwFoAU
+wDFSzVpQw4J8dHHOy+mc+XrrguIwHQYDVR0OBBYEFFgCnhEuuG5ptVcvPfBJZssU
+U6jFMBcGA1UdEQQQMA6CDCouZG9ja2VyLmNvbTATBgNVHSAEDDAKMAgGBmeBDAEC
+ATAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC
+MDsGA1UdHwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmwucjJtMDIuYW1hem9udHJ1c3Qu
+Y29tL3IybTAyLmNybDB1BggrBgEFBQcBAQRpMGcwLQYIKwYBBQUHMAGGIWh0dHA6
+Ly9vY3NwLnIybTAyLmFtYXpvbnRydXN0LmNvbTA2BggrBgEFBQcwAoYqaHR0cDov
+L2NydC5yMm0wMi5hbWF6b250cnVzdC5jb20vcjJtMDIuY2VyMAwGA1UdEwEB/wQC
+MAAwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB3AN3cyjSV1+EWBeeVMvrHn/g9
+HFDf2wA6FBJ2Ciysu8gqAAABkWTCKI4AAAQDAEgwRgIhALzND2kn+PR4aObDiWGv
+7QJZ/z4i/vhAGjd/vp2hjt4HAiEAytgsAcPUFd/Y98n5EQkMg6GBSUUygBMc7+Xk
+UVJXFgcAdQDm0jFjQHeMwRBBBtdxuc7B0kD2loSG+7qHMh39HjeOUAAAAZFkwiic
+AAAEAwBGMEQCIGXbP5ughe+EksczYEErOqY1LZHi9/SmXcp6/vAeuXBmAiB16Sw7
+1sPB/CpKq75/pEwb5pamTdmeEIaHtGQTkTMZ5QB2AMz7D2qFcQll/pWbU87psnwi
+6YVcDZeNtql+VMD+TA2wAAABkWTCKIoAAAQDAEcwRQIgX2WsD3ThDwalMCYqmf+X
+ICS9imyYmwKXJbfkSnMH/HkCIQCEkDrOAJZG1fGfyOzhSPHWPMxbuGwV18jBGe52
+D9PPQTANBgkqhkiG9w0BAQsFAAOCAQEAcW8Z3+JzqfGTYX+MBk2pQvo8msx+fINU
+ZzORUcFUNV0467G/Kc780S8GRxF8dr90WaFctMvw8yDOipfj0sacGJFVFFf5XoWu
+1EqYyx3hgMLCQ5DzjAoY4X1KsfPRBe2DCsqf+Nt/TVzgVjOSglURBWKV2T+Av78H
+HiMjtEUZbatvuKvUg5S26dvUxXseN/8Jbbt9MCCGascPOf+zXGysoBSwglt0Nz7W
+hjXnY+BPeLD38ouUGx0R+sZbXICR6BIxtT4GgOcmJJ/KT3fcMObhsihiq6lO1aql
+Yo4AvWpg2tJtwFrG4ooDRyyARljHMUMIkuTz9Dypl/dnJJfzAQLjVA==
+-----END CERTIFICATE-----
diff --git a/src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem b/src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem
new file mode 100644
index 00000000..b85c8037
--- /dev/null
+++ b/src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
+WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
+ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
+h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
+A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
+T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
+B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
+B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
+KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
+OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
+jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
+qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
+rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
+hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
+ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
+3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
+NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
+ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
+TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
+jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
+oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
+4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
+mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
+emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
+-----END CERTIFICATE-----


Reviews:
user: login: karel-rehor
body: Still in progress.  But I've raised a couple of questions in the config file.  

I was surprised to see that `io.grpc.netty.GrpcSslContexts` is tagged as `@experimental` and would not want to use "experimental" code in a public library.  However, after digging around in https://github.com/grpc/grpc-java/blob/master/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java I see that it has been _experimental_ for _9_ years... :confused:

TBC 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:34:24Z
user: login: karel-rehor
body: To start, I think the ClientConfig class needs to be simplified a bit, so that end-users don't need to explicitly import the same dependencies used in the library, and don't need to supply the likely same value more than once.  
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T10:01:20Z
user: login: jansimonb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T10:24:11Z
user: login: NguyenHoangSon96
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T13:59:45Z
user: login: jansimonb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:43:11Z
user: login: NguyenHoangSon96
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T02:53:21Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-20T08:01:40Z
user: login: karel-rehor
body: Review is still in progress.  Mainly at this point I have some questions that need to be resolved before continuing.  However, the example does not compile when opened in its own Maven project.  
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T13:55:12Z
user: login: jansimonb
body: looks good with few comments
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T14:20:12Z
user: login: NguyenHoangSon96
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T04:16:31Z
user: login: jansimonb
body: Thanks for the changes! Few more comments.

Also, could you please also increase the code coverage of `FlightSqlClient.java` if possible?
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T09:57:13Z
user: login: jansimonb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T11:56:55Z
user: login: jansimonb
body: LGTM, thanks! 👍 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T08:21:50Z
user: login: karel-rehor
body: Looks pretty good.  I tried to run the example again, but found an inconsistency between the value for the written measurement ("Home") and the value for the measurement in the query string ("home"). You may want to take a second look.   

I also took the liberty of making a minor change to E2ETest.  If a test gets skipped it needs to be marked as _skipped_ by junit as well.  
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T13:30:28Z
user: login: karel-rehor
body: The new example still doesn't work for me.  Reasons and suggestions are in the line comment. 
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T13:04:26Z
user: login: NguyenHoangSon96
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T01:07:54Z
user: login: karel-rehor
body: Looks good.  Example works for me now. 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T09:21:12Z

Review comments:
diff_hunk: @@ -97,8 +102,11 @@ public final class ClientConfig {
     private final Boolean allowHttpRedirects;
     private final Boolean disableServerCertificateValidation;
     private final ProxySelector proxy;
+    private final ProxyDetector queryApiProxy;
     private final Authenticator authenticator;
     private final Map<String, String> headers;
+    private final SslContext grpcSslContext;
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: karel-rehor
body: I don't see either `grpcSslContext` or `sslContext` in the Javadoc above the class.  
created_at: 2025-03-12T15:46:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,8 +102,11 @@ public final class ClientConfig {
     private final Boolean allowHttpRedirects;
     private final Boolean disableServerCertificateValidation;
     private final ProxySelector proxy;
+    private final ProxyDetector queryApiProxy;
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: karel-rehor
body: This self-descriptive naming might be more precise as `grpcApiProxy` or `grpcProxy`.  It would also match with the `grpcSslContext` naming below. 
created_at: 2025-03-12T15:50:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,80 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.util.stream.Stream;
+
+import io.grpc.HttpConnectProxiedSocketAddress;
+import io.grpc.ProxyDetector;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() { }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        URI queryProxyUri = new URI("proxyUrl");
+        URI uri = new URI(System.getenv("url"));
+
+        ProxyDetector proxyDetector = (targetServerAddress) -> {
+            InetSocketAddress targetAddress = (InetSocketAddress) targetServerAddress;
+            if (uri.getHost().equals(targetAddress.getHostString())) {
+                return HttpConnectProxiedSocketAddress.newBuilder()
+                        .setProxyAddress(new InetSocketAddress(queryProxyUri.getHost(), queryProxyUri.getPort()))
+                        .setTargetAddress(targetAddress)
+                        .build();
+            }
+            return null;
+        };
+        ProxySelector proxy = ProxySelector.of(new InetSocketAddress(queryProxyUri.getHost(), queryProxyUri.getPort()));
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: jansimonb
body: Shouldn't be better to hide `ProxyDetector` and `ProxySelector` inside the `InfluxDBClient`? I think if `ClientConfig` would have a `proxyUrl` field then the whole setup would be much simpler.
created_at: 2025-03-13T09:25:09Z
author_association: CONTRIBUTOR
path: examples/docker-compose.yml
user: login: jansimonb
body: minor: It'd be nice to document why do we start envoy proxy. Do we need it just for local testing? Or for CicrleCI tests?
created_at: 2025-03-13T09:40:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,80 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.util.stream.Stream;
+
+import io.grpc.HttpConnectProxiedSocketAddress;
+import io.grpc.ProxyDetector;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() { }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        URI queryProxyUri = new URI("proxyUrl");
+        URI uri = new URI(System.getenv("url"));
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: jansimonb
body: minor: It's not clear what should be changed/configured to start this test. I'd either get both urls from ENV or would both hardcode like in `IOxExample`. Also env variable names used to be uppercase (e.g. `INFLUXDB_URL`, `PROXY_URL`).
created_at: 2025-03-13T09:41:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,8 +102,11 @@ public final class ClientConfig {
     private final Boolean allowHttpRedirects;
     private final Boolean disableServerCertificateValidation;
     private final ProxySelector proxy;
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: karel-rehor
body: I think that the dependency library classes `java.net.ProxySelector`, `io.grpc.ProxyDetector`, `java.net.ssl.SSLContext` and `io.netty.handler.ssl.SslContext`, should be handled internally by our library, and should not be exposed to the end user in the ClientConfig.  It will be easier if the enduser needs to supply the values for `proxyUrl` and `sslRootsFilePath` only once, since the same value will in all probability be used in instantiating both the grpc and rest clients.

Compare this config to the CSharp ClientConfig. 

https://github.com/InfluxCommunity/influxdb3-csharp/blob/7757689fef976a032c79dcfb80f43967a473c2b9/Client/Config/ClientConfig.cs#L8
created_at: 2025-03-13T09:58:39Z
author_association: CONTRIBUTOR
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: jansimonb
body: I am missing an example code for SSL configuration. Also have you tested it with both SSL + Proxy set?
created_at: 2025-03-13T10:24:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,8 +102,11 @@ public final class ClientConfig {
     private final Boolean allowHttpRedirects;
     private final Boolean disableServerCertificateValidation;
     private final ProxySelector proxy;
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: NguyenHoangSon96
body: Hi @karel-rehor, @jansimonb 
Thanks for the reviews and suggestions guys 😃
I thought about using something like "sslRootsFilePath" after reading SSL implementation in the Golang client, but
when I try it in Java I can't "append" new certs to the default certs like in Golang.
In Java if we point to the custom certification file it will "override" all the certifications in the default "cacert" file.
If I want to "append", I have to read the default "cacert" file and then append the new certifications to it
That is complicated and the users needed to give a password to read the default "cacert" file.
That why I just let them pass the SslContext and do whatever they want 😀
I'm not sure how this works in C# though.
 
 
  
created_at: 2025-03-13T13:59:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -97,8 +102,11 @@ public final class ClientConfig {
     private final Boolean allowHttpRedirects;
     private final Boolean disableServerCertificateValidation;
     private final ProxySelector proxy;
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: jansimonb
body: IMHO, it's not a problem if the root certs from system are not included/copied. This client connects to a single Influxdb server and the user will configure custom ssl certificate only if it's required. And in that case will pass the certificate for that Influxdb server. This is not a web browser, so we don't need many CA roots, usually just a single one.
created_at: 2025-03-13T14:43:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,80 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.util.stream.Stream;
+
+import io.grpc.HttpConnectProxiedSocketAddress;
+import io.grpc.ProxyDetector;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() { }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        URI queryProxyUri = new URI("proxyUrl");
+        URI uri = new URI(System.getenv("url"));
+
+        ProxyDetector proxyDetector = (targetServerAddress) -> {
+            InetSocketAddress targetAddress = (InetSocketAddress) targetServerAddress;
+            if (uri.getHost().equals(targetAddress.getHostString())) {
+                return HttpConnectProxiedSocketAddress.newBuilder()
+                        .setProxyAddress(new InetSocketAddress(queryProxyUri.getHost(), queryProxyUri.getPort()))
+                        .setTargetAddress(targetAddress)
+                        .build();
+            }
+            return null;
+        };
+        ProxySelector proxy = ProxySelector.of(new InetSocketAddress(queryProxyUri.getHost(), queryProxyUri.getPort()));
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: NguyenHoangSon96
body: @jansimonb 
I seperated 2 proxies because I don't know if there are cases that users want to use 2 different proxy urls or not...
But you're right, pass a proxy string to our client will be much more simpler 👌
I will refactor this
created_at: 2025-03-14T02:53:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -107,11 +122,14 @@
 
         if (baseUrl.startsWith("https")) {
             try {
-                if (config.getDisableServerCertificateValidation()) {
-                    SSLContext sslContext = SSLContext.getInstance("TLS");
-                    sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom());
-                    builder.sslContext(sslContext);
+                SSLContext sslContext = SSLContext.getInstance("TLS");
+                sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom());
path: src/main/java/com/influxdb/v3/client/internal/RestClient.java
user: login: github-advanced-security[bot]
body: ## `TrustManager` that accepts all certificates

This uses [TrustManager](1), which is defined in [RestClient$](2) and trusts any certificate.

[Show more details](https://github.com/InfluxCommunity/influxdb3-java/security/code-scanning/1)
created_at: 2025-03-20T08:01:40Z
author_association: NONE
diff_hunk: @@ -58,14 +59,19 @@
  *          disable server certificate validation for HTTPS connections
  *     </li>
  *     <li><code>proxy</code> - HTTP proxy selector</li>
+ *     <li><code>proxyAddress</code> - Proxy address for query api and write api</li>
+ *     <li><code>queryApiProxy</code> - HTTP query detector</li>
  *     <li><code>authenticator</code> - HTTP proxy authenticator</li>
  *     <li><code>headers</code> - headers to be added to requests</li>
+ *     <li><code>certificateFilePath</code> - Path to the stored certificates file</li>
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: jansimonb
body: It'd be good to distinguish whether it's a client or server certificate. Something like `sslRootsFilePath` or `serverRootCertsFilePath` or `trustedServerCertsFilePath`.
Please, also document that it's in PEM format.
created_at: 2025-03-20T11:07:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,14 +59,19 @@
  *          disable server certificate validation for HTTPS connections
  *     </li>
  *     <li><code>proxy</code> - HTTP proxy selector</li>
+ *     <li><code>proxyAddress</code> - Proxy address for query api and write api</li>
+ *     <li><code>queryApiProxy</code> - HTTP query detector</li>
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: jansimonb
body: `queryApiProxy` does not exist anymore
created_at: 2025-03-20T11:08:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,14 +59,19 @@
  *          disable server certificate validation for HTTPS connections
  *     </li>
  *     <li><code>proxy</code> - HTTP proxy selector</li>
+ *     <li><code>proxyAddress</code> - Proxy address for query api and write api</li>
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: jansimonb
body: ```suggestion
 *     <li><code>proxyAddress</code> - Proxy URL address for query api and write api</li>
```
created_at: 2025-03-20T11:09:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() { }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyAddress = new InetSocketAddress("localhost", 10000);
+        String certificateFilePath = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("TESTING_INFLUXDB_URL"))
+                .token(System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("TESTING_INFLUXDB_DATABASE"))
+                .proxyAddress(proxyAddress)
+                .certificateFilePath(certificateFilePath)
+                .build();
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: jansimonb
body: minor: I'd separate the inputs from the test, to make it clean what should be changed
created_at: 2025-03-20T11:22:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -318,8 +356,10 @@ public static final class Builder {
         private Boolean allowHttpRedirects;
         private Boolean disableServerCertificateValidation;
         private ProxySelector proxy;
+        private InetSocketAddress proxyAddress;
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: jansimonb
body: Keep it simple for the user, use `String proxyUrl`, that could be loaded even from environment variable, don't require `InetSocketAddress`
created_at: 2025-03-20T11:24:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,9 +101,16 @@ public final class ClientConfig {
     private final Duration timeout;
     private final Boolean allowHttpRedirects;
     private final Boolean disableServerCertificateValidation;
-    private final ProxySelector proxy;
+    private final InetSocketAddress proxyAddress;
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: jansimonb
body: ```suggestion
    private final String proxyUrl;
```
created_at: 2025-03-20T12:40:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() { }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyAddress = new InetSocketAddress("localhost", 10000);
+        String certificateFilePath = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("TESTING_INFLUXDB_URL"))
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: karel-rehor
body: The environment variables do not need to have the `TESTING_` token as part of the key. In `RetryExample.java` for example the following variables where used.      `INFLUX_HOST`, `INFLUX_TOKEN`, `INFLUX_DATABASE`.  I see in other examples no environment variables were used.  Maybe this should be standardized.  But `TESTING_` suggests a copy paste from integration tests, and will be unnecessary for end users. 
created_at: 2025-03-20T12:49:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() { }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyAddress = new InetSocketAddress("localhost", 10000);
+        String certificateFilePath = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("TESTING_INFLUXDB_URL"))
+                .token(System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("TESTING_INFLUXDB_DATABASE"))
+                .proxyAddress(proxyAddress)
+                .certificateFilePath(certificateFilePath)
+                .build();
+
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        influxDBClient.writePoint(
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: karel-rehor
body: It might be better to have some more interesting data, like for a drone, a vehicle or a home IoT device.  
created_at: 2025-03-20T12:54:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() { }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyAddress = new InetSocketAddress("localhost", 10000);
+        String certificateFilePath = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("TESTING_INFLUXDB_URL"))
+                .token(System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("TESTING_INFLUXDB_DATABASE"))
+                .proxyAddress(proxyAddress)
+                .certificateFilePath(certificateFilePath)
+                .build();
+
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        influxDBClient.writePoint(
+                Point.measurement("test1")
+                        .setField("field", "field1")
+        );
+
+        try (Stream<PointValues> stream = influxDBClient.queryPoints("SELECT * FROM test1")) {
+            stream.findFirst()
+                    .ifPresent(pointValues -> {
+                        Assertions.assertThat(pointValues.getField("field")).isEqualTo("field1");
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: karel-rehor
body: Other examples write results to STDOUT.  This gives the end user more of a participatory sense that one or more transactions have been successfully completed. 

Also... 
![Screenshot from 2025-03-20 14-23-48](https://github.com/user-attachments/assets/2a59d61e-38b3-49ba-a8a6-96561e088bf8)

created_at: 2025-03-20T12:56:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() { }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyAddress = new InetSocketAddress("localhost", 10000);
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: karel-rehor
body: `String` type cannot be cast from an `InetSocketAddress` type.  Also `java.net.InetSocketAddress` needs to be imported. 

![Screenshot from 2025-03-20 14-22-18](https://github.com/user-attachments/assets/5085919d-a57c-4044-8cb5-700c7befc45f)

created_at: 2025-03-20T13:21:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -480,6 +521,19 @@ public Builder proxy(@Nullable final ProxySelector proxy) {
             return this;
         }
 
+        /**
+         * Sets the proxyAddress. Default is 'null'.
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: jansimonb
body: ```suggestion
         * Sets the proxy URL. Default is 'null'.
```
created_at: 2025-03-20T13:27:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,62 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() { }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyAddress = new InetSocketAddress("localhost", 10000);
+        String certificateFilePath = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("TESTING_INFLUXDB_URL"))
+                .token(System.getenv("TESTING_INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("TESTING_INFLUXDB_DATABASE"))
+                .proxyAddress(proxyAddress)
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: karel-rehor
body: So should `proxyAddress` be a `String` or `InetSocketAddress`?  So that users don't have to use too many imports, I'd prefer using more primitive or _core_ classes in the configuration as much as possible.  Passing these values to other libraries should also be handled internally as much as possible. 

![Screenshot from 2025-03-20 14-23-17](https://github.com/user-attachments/assets/d1c953a4-5458-4700-9326-d9b82803e059)

created_at: 2025-03-20T13:30:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -127,14 +143,68 @@ public void close() throws Exception {
     }
 
     @Nonnull
-    private FlightClient createFlightClient(@Nonnull final ClientConfig config) {
+    FlightClient createFlightClient(@Nonnull final ClientConfig config) {
path: src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java
user: login: jansimonb
body: unnecessary visibility change
created_at: 2025-03-20T13:33:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -127,14 +143,68 @@ public void close() throws Exception {
     }
 
     @Nonnull
-    private FlightClient createFlightClient(@Nonnull final ClientConfig config) {
+    FlightClient createFlightClient(@Nonnull final ClientConfig config) {
         Location location = createLocation(config);
 
-        return FlightClient.builder()
-                .location(location)
-                .allocator(new RootAllocator(Long.MAX_VALUE))
-                .verifyServer(!config.getDisableServerCertificateValidation())
-                .build();
+        final NettyChannelBuilder nettyChannelBuilder = NettyChannelBuilder.forTarget(location.getUri().getHost());
+        var validSchemas = List.of(
+                LocationSchemes.GRPC,
+                LocationSchemes.GRPC_INSECURE,
+                LocationSchemes.GRPC_TLS,
+                LocationSchemes.GRPC_DOMAIN_SOCKET
+        );
path: src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java
user: login: jansimonb
body: minor: could be a static constant on class level
created_at: 2025-03-20T13:35:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,9 +101,16 @@ public final class ClientConfig {
     private final Duration timeout;
     private final Boolean allowHttpRedirects;
     private final Boolean disableServerCertificateValidation;
-    private final ProxySelector proxy;
+    private final InetSocketAddress proxyAddress;
path: src/main/java/com/influxdb/v3/client/config/ClientConfig.java
user: login: karel-rehor
body: I would prefer a String type here if possible, so that users don't also have to add `import java.net.InetSocketAddress;`. From the library point of view`InetSocketAddress` can be handled internally using values from the parsed proxy string.  

Also can this be added to the unit tests in https://github.com/InfluxCommunity/influxdb3-java/blob/main/src/test/java/com/influxdb/v3/client/config/ClientConfigTest.java ?
created_at: 2025-03-20T13:41:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -166,6 +236,56 @@ private HeaderCallOption metadataHeader(@Nonnull final Map<String, String> reque
         return new HeaderCallOption(metadata);
     }
 
+    private void setChannelTypeAndEventLoop(@Nonnull final NettyChannelBuilder nettyChannelBuilder) {
+        // The implementation is platform-specific, so we have to find the classes at runtime
+        try {
+            try {
+                // Linux
path: src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java
user: login: karel-rehor
body: If this is platform specific is there a problem with using `System.getProperty("os.name")` with a switch instead of executing and then catching exceptions?  Also, is there a Windows specific `channelType`?
created_at: 2025-03-20T13:52:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -107,11 +119,14 @@ public void checkServerTrusted(
 
         if (baseUrl.startsWith("https")) {
             try {
-                if (config.getDisableServerCertificateValidation()) {
-                    SSLContext sslContext = SSLContext.getInstance("TLS");
-                    sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom());
-                    builder.sslContext(sslContext);
+                SSLContext sslContext = SSLContext.getInstance("TLS");
+                sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom());
+
+                if (config.certificateFilePath() != null && !config.getDisableServerCertificateValidation()) {
+                    X509TrustManager x509TrustManager = getX509TrustManagerFromFile(config.certificateFilePath());
+                    sslContext.init(null, new X509TrustManager[]{x509TrustManager}, new SecureRandom());
                 }
path: src/main/java/com/influxdb/v3/client/internal/RestClient.java
user: login: jansimonb
body: minor: alternatively:
```suggestion
                if (config.certificateFilePath() != null && !config.getDisableServerCertificateValidation()) {
                    X509TrustManager x509TrustManager = getX509TrustManagerFromFile(config.certificateFilePath());
                    sslContext.init(null, new X509TrustManager[]{x509TrustManager}, new SecureRandom());
                } else {
                    sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom());
                }
```
created_at: 2025-03-20T13:56:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,22 +21,133 @@
  */
 package com.influxdb.v3.client;
 
+import java.io.IOException;
 import java.math.BigInteger;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.net.URLConnection;
 import java.time.Instant;
 import java.util.Map;
 import java.util.Properties;
 import java.util.UUID;
 import java.util.stream.Stream;
+import javax.annotation.Nonnull;
 
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
 
+import com.influxdb.v3.client.config.ClientConfig;
 import com.influxdb.v3.client.write.WriteOptions;
 import com.influxdb.v3.client.write.WritePrecision;
 
 public class InfluxDBClientTest {
 
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_URL", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_TOKEN", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_DATABASE", matches = ".*")
+    @Test
+    void testQueryWithProxy() {
+        InetSocketAddress proxyAddress = new InetSocketAddress("localhost", 10000);
+
+        try {
+            // Continue to run this test only if Envoy proxy is running in this address http://127.0.0.1:10000
+            String url = String.format("http://%s:%d/", proxyAddress.getHostName(), proxyAddress.getPort());
+            URLConnection hpCon = new URL(url).openConnection();
+            hpCon.connect();
+        } catch (IOException e) {
+            return;
path: src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java
user: login: jansimonb
body: It would be nice at least print a warning that the test is skipped due to proxy.. This way somebody could think everything is OK, but the proxy feature is not tested at all.

(Personally I don't like the `@EnabledIfEnvironmentVariable` either. We should split the tests into unit and integration tests and require these environment variables for integration tests. And not just silently skip tests.)
created_at: 2025-03-20T14:08:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -173,4 +284,17 @@ public void testQuery() throws Exception {
             }
         }
     }
+
+    private void assertGetdataSuccess(@Nonnull final InfluxDBClient influxDBClient) throws Exception {
path: src/test/java/com/influxdb/v3/client/InfluxDBClientTest.java
user: login: jansimonb
body: ```suggestion
    private void assertGetDataSuccess(@Nonnull final InfluxDBClient influxDBClient) throws Exception {
```
created_at: 2025-03-20T14:10:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -166,6 +236,56 @@ private HeaderCallOption metadataHeader(@Nonnull final Map<String, String> reque
         return new HeaderCallOption(metadata);
     }
 
+    private void setChannelTypeAndEventLoop(@Nonnull final NettyChannelBuilder nettyChannelBuilder) {
+        // The implementation is platform-specific, so we have to find the classes at runtime
+        try {
+            try {
+                // Linux
path: src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java
user: login: NguyenHoangSon96
body: Hi @karel-rehor 
![Screenshot 2025-03-24 at 11 10 20](https://github.com/user-attachments/assets/01c6e4f1-39ca-4721-89b6-366040b8cc44)
There must be some special reasons so they have to separate Linux and BSD, the other OS is fine.
I just copied this code from arrow flight library and refactored
If we change to System.getProperty("os.name"), I'm not sure what it will return when our client runs on BSD system 
created_at: 2025-03-24T04:16:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,5 +1,9 @@
 ## 1.1.0 [unreleased]
 
+### Features
+
+1. [#229](https://github.com/InfluxCommunity/influxdb3-java/pull/229): Support proxy and ssl
path: CHANGELOG.md
user: login: jansimonb
body: ```suggestion
1. [#229](https://github.com/InfluxCommunity/influxdb3-java/pull/229): Support proxy and custom ssl root certificates
```
created_at: 2025-03-24T08:52:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -130,11 +152,59 @@ public void close() throws Exception {
     private FlightClient createFlightClient(@Nonnull final ClientConfig config) {
         Location location = createLocation(config);
 
-        return FlightClient.builder()
-                .location(location)
-                .allocator(new RootAllocator(Long.MAX_VALUE))
-                .verifyServer(!config.getDisableServerCertificateValidation())
-                .build();
+        final NettyChannelBuilder nettyChannelBuilder = NettyChannelBuilder.forTarget(location.getUri().getHost());
+        if (!validSchemas.contains(location.getUri().getScheme())) {
+            throw new IllegalArgumentException(
+                    "Scheme is not supported: " + location.getUri().getScheme());
+        }
+
+        if (location.getUri().getScheme().equals(LocationSchemes.GRPC_DOMAIN_SOCKET)) {
+            setChannelTypeAndEventLoop(nettyChannelBuilder);
+        }
+
+        if (LocationSchemes.GRPC_TLS.equals(location.getUri().getScheme())) {
+            nettyChannelBuilder.useTransportSecurity();
+
+            SslContext nettySslContext = createNettySslContext(config);
+            nettyChannelBuilder.sslContext(nettySslContext);
+        } else {
+            nettyChannelBuilder.usePlaintext();
+        }
+
+        if (config.getProxyUrl() != null) {
+            ProxyDetector proxyDetector = createProxyDetector(config.getHost(), config.getProxyUrl());
+            nettyChannelBuilder.proxyDetector(proxyDetector);
+        }
+
+        if (config.getProxy() != null) {
+            LOG.warn("proxy property in ClientConfig will not work in query api, use proxyUrl property instead");
+        }
+
+        nettyChannelBuilder.maxTraceEvents(0)
+                .maxInboundMessageSize(Integer.MAX_VALUE)
+                .maxInboundMetadataSize(Integer.MAX_VALUE);
+
+        return FlightGrpcUtils.createFlightClient(new RootAllocator(Long.MAX_VALUE), nettyChannelBuilder.build());
+    }
+
+    @Nonnull
+    SslContext createNettySslContext(@Nonnull final ClientConfig config) {
+        try {
+            SslContextBuilder sslContextBuilder;
+            sslContextBuilder = GrpcSslContexts.forClient();
+            if (!config.getDisableServerCertificateValidation()) {
+                if (config.sslRootsFilePath() != null) {
+                    try (FileInputStream fileInputStream = new FileInputStream(config.sslRootsFilePath())) {
+                        sslContextBuilder.trustManager(fileInputStream);
+                    }
+                }
+            } else {
+                sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
+            }
path: src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java
user: login: jansimonb
body: In my previous review I missed this detail. If `sslRootsFilePath` is not provided that does not mean that we are disabling the server verification but we are using operating system's default trust store for verification.
```suggestion
            if (config.getDisableServerCertificateValidation()) {
                sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
            } else if (config.sslRootsFilePath() != null) {
                try (FileInputStream fileInputStream = new FileInputStream(config.sslRootsFilePath())) {
                    sslContextBuilder.trustManager(fileInputStream);
                }
            }
```
created_at: 2025-03-24T09:40:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -107,11 +122,14 @@ public void checkServerTrusted(
 
         if (baseUrl.startsWith("https")) {
             try {
-                if (config.getDisableServerCertificateValidation()) {
-                    SSLContext sslContext = SSLContext.getInstance("TLS");
+                SSLContext sslContext = SSLContext.getInstance("TLS");
+                if (config.sslRootsFilePath() != null && !config.getDisableServerCertificateValidation()) {
+                    X509TrustManager x509TrustManager = getX509TrustManagerFromFile(config.sslRootsFilePath());
+                    sslContext.init(null, new X509TrustManager[]{x509TrustManager}, new SecureRandom());
+                } else {
path: src/main/java/com/influxdb/v3/client/internal/RestClient.java
user: login: jansimonb
body: In my previous review I missed this detail. If `sslRootsFilePath` is not provided that does not mean that we are disabling the server verification but we are using operating system's default trust store for verification.
```java
                if (config.getDisableServerCertificateValidation()) {
                    sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom());
                } else if (config.sslRootsFilePath() != null) {
                    X509TrustManager x509TrustManager = getX509TrustManagerFromFile(config.sslRootsFilePath());
                    sslContext.init(null, new X509TrustManager[]{x509TrustManager}, new SecureRandom());
                }
```
created_at: 2025-03-24T09:44:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -107,11 +122,15 @@ public void checkServerTrusted(
 
         if (baseUrl.startsWith("https")) {
             try {
+                SSLContext sslContext = SSLContext.getInstance("TLS");
                 if (config.getDisableServerCertificateValidation()) {
-                    SSLContext sslContext = SSLContext.getInstance("TLS");
                     sslContext.init(null, TRUST_ALL_CERTS, new SecureRandom());
-                    builder.sslContext(sslContext);
+                } else if (config.sslRootsFilePath() != null) {
+                    X509TrustManager x509TrustManager = getX509TrustManagerFromFile(config.sslRootsFilePath());
+                    sslContext.init(null, new X509TrustManager[]{x509TrustManager}, new SecureRandom());
                 }
+                sslContext.init(null, null, new SecureRandom());
path: src/main/java/com/influxdb/v3/client/internal/RestClient.java
user: login: jansimonb
body: I think this should go into an `else` branch.
created_at: 2025-03-24T11:56:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() {
+    }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyUrl = "http://localhost:10000";
+        String sslRootsFilePath = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("INFLUXDB_URL"))
+                .token(System.getenv("INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("INFLUXDB_DATABASE"))
+                .proxyUrl(proxyUrl)
+                .sslRootsFilePath(sslRootsFilePath)
+                .build();
+
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        Point point = Point.measurement("Home")
+                .setTag("room", "Kitchen")
+                .setField("temp", 12.7)
+                .setField("hum", 37);
+        influxDBClient.writePoint(point);
+
+        try (Stream<PointValues> stream = influxDBClient.queryPoints("SELECT * FROM home")) {
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: karel-rehor
body: measurement name here is `home` but in the point it is `Home`.  When testing this example as is I get an exception here.

```
Exception in thread "main" org.apache.arrow.flight.FlightRuntimeException: INVALID_ARGUMENT: Error while planning query: Error during planning: table 'public.iox.home' not found

``` 

This works for me... 
```java
        try (Stream<PointValues> stream = influxDBClient.queryPoints("SELECT * FROM \"Home\"")) {
            System.out.println("DEBUG have stream");
            stream.findFirst().ifPresent(System.out::println);
        }
```
created_at: 2025-03-25T12:45:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,210 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3.client.integration;
+
+import java.math.BigInteger;
+import java.net.ConnectException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.time.Instant;
+import java.util.Map;
+import java.util.UUID;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+import javax.annotation.Nonnull;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+import com.influxdb.v3.client.write.WriteOptions;
+import com.influxdb.v3.client.write.WritePrecision;
+
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+
+public class E2ETest {
+
+    private static final java.util.logging.Logger LOG = Logger.getLogger(E2ETest.class.getName());
+
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_URL", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_TOKEN", matches = ".*")
+    @EnabledIfEnvironmentVariable(named = "TESTING_INFLUXDB_DATABASE", matches = ".*")
+    @Test
+    void testQueryWithProxy() {
+        String proxyUrl = "http://localhost:10000";
+
+        try {
+            // Continue to run this test only if Envoy proxy is running on this address http://localhost:10000
+            String url = String.format("http://%s:%d", "localhost", 10000);
+            URLConnection hpCon = new URL(url).openConnection();
+            hpCon.connect();
+        } catch (Exception e) {
+            if (e instanceof ConnectException && e.getMessage().contains("Connection refused")) {
+                LOG.warning("Tests with proxy have been skipped because no proxy is running on " + proxyUrl);
+                assumeFalse(e.getMessage().contains("Connection refused"));
path: src/test/java/com/influxdb/v3/client/integration/E2ETest.java
user: login: karel-rehor
body: I added this line so that this test will be marked _skipped_ by junit when the proxy is not running.  I noticed it was marked as passing.  
created_at: 2025-03-25T13:22:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() {
+    }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyUrl = "http://localhost:10000";
+        String sslRootsFilePath = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("INFLUXDB_URL"))
+                .token(System.getenv("INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("INFLUXDB_DATABASE"))
+                .proxyUrl(proxyUrl)
+                .sslRootsFilePath(sslRootsFilePath)
+                .build();
+
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        Point point = Point.measurement("Home")
+                .setTag("room", "Kitchen")
+                .setField("temp", 12.7)
+                .setField("hum", 37);
+        influxDBClient.writePoint(point);
+
+        try (Stream<PointValues> stream = influxDBClient.queryPoints("SELECT * FROM Home")) {
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: karel-rehor
body: I think in this query `Home` needs to be surrounded by double quotes.  

I've tried this and get the same error as before. I seem to recall seeing an issue regrading this in the past, that measurement names that don't use lower case need to be quoted in queries because of how the server handles them on the backend, but can't find it right now.   
```
Exception in thread "main" org.apache.arrow.flight.FlightRuntimeException: INVALID_ARGUMENT: Error while planning query: Error during planning: table 'public.iox.home' not found
```
However this works: 

```java
        try (Stream<PointValues> stream = influxDBClient.queryPoints("SELECT * FROM \"Home\"")) {
            stream.findFirst().ifPresent((values) -> {
              assert values.getTimestamp() != null;
              System.out.printf("room[%s]: %s, temp: %3.2f, hum: %d",
                  new java.util.Date(values.getTimestamp().longValue() / 1000000),
                  values.getTag("room"),
                  (Double)values.getField("temp"),
                  (Long)values.getField("hum"));
            });
        }
```
result
```
room[Wed Mar 26 13:49:58 CET 2025]: Kitchen, temp: 12.70, hum: 37
```
BTW it would be nicer if the output were formatted as it is here, instead of just writing the standard `.toString()` with package, classname and hash value.  
created_at: 2025-03-26T13:03:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.influxdb.v3;
+
+import java.util.stream.Stream;
+
+import com.influxdb.v3.client.InfluxDBClient;
+import com.influxdb.v3.client.Point;
+import com.influxdb.v3.client.PointValues;
+import com.influxdb.v3.client.config.ClientConfig;
+
+public final class ProxyExample {
+
+    private ProxyExample() {
+    }
+
+    public static void main(final String[] args) throws Exception {
+        // Run docker-compose.yml file to start Envoy proxy
+
+        String proxyUrl = "http://localhost:10000";
+        String sslRootsFilePath = "src/test/java/com/influxdb/v3/client/testdata/influxdb-certificate.pem";
+        ClientConfig clientConfig = new ClientConfig.Builder()
+                .host(System.getenv("INFLUXDB_URL"))
+                .token(System.getenv("INFLUXDB_TOKEN").toCharArray())
+                .database(System.getenv("INFLUXDB_DATABASE"))
+                .proxyUrl(proxyUrl)
+                .sslRootsFilePath(sslRootsFilePath)
+                .build();
+
+        InfluxDBClient influxDBClient = InfluxDBClient.getInstance(clientConfig);
+        Point point = Point.measurement("Home")
+                .setTag("room", "Kitchen")
+                .setField("temp", 12.7)
+                .setField("hum", 37);
+        influxDBClient.writePoint(point);
+
+        try (Stream<PointValues> stream = influxDBClient.queryPoints("SELECT * FROM Home")) {
path: examples/src/main/java/com/influxdb/v3/ProxyExample.java
user: login: NguyenHoangSon96
body: Hi @karel-rehor 
Thanks for pointing this out
Can you please check again 🙏
<img width="1552" alt="Screenshot 2025-03-27 at 08 05 31" src="https://github.com/user-attachments/assets/325e4905-379a-48b6-8f84-05d8e4b9bbcf" />


created_at: 2025-03-27T01:07:54Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-03-12T10:54:25Z
body: ## [Codecov](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/pull/230?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity) Report
Attention: Patch coverage is `83.13253%` with `14 lines` in your changes missing coverage. Please review.
> Project coverage is 90.05%. Comparing base [(`8db709b`)](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/commit/8db709b7982e050fd1c141a2a0e9b662a95a3ee8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity) to head [(`9008c68`)](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/commit/9008c68be049f13950a7fda24dbc690ff2ac7845?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity).
> Report is 52 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/pull/230?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity) | Patch % | Lines |
|---|---|---|
| [...m/influxdb/v3/client/internal/FlightSqlClient.java](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/pull/230?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fcom%2Finfluxdb%2Fv3%2Fclient%2Finternal%2FFlightSqlClient.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity#diff-c3JjL21haW4vamF2YS9jb20vaW5mbHV4ZGIvdjMvY2xpZW50L2ludGVybmFsL0ZsaWdodFNxbENsaWVudC5qYXZh) | 78.37% | [5 Missing and 3 partials :warning: ](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/pull/230?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity) |
| [...va/com/influxdb/v3/client/config/ClientConfig.java](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/pull/230?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fcom%2Finfluxdb%2Fv3%2Fclient%2Fconfig%2FClientConfig.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity#diff-c3JjL21haW4vamF2YS9jb20vaW5mbHV4ZGIvdjMvY2xpZW50L2NvbmZpZy9DbGllbnRDb25maWcuamF2YQ==) | 76.92% | [0 Missing and 3 partials :warning: ](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/pull/230?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity) |
| [...va/com/influxdb/v3/client/internal/RestClient.java](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/pull/230?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fcom%2Finfluxdb%2Fv3%2Fclient%2Finternal%2FRestClient.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity#diff-c3JjL21haW4vamF2YS9jb20vaW5mbHV4ZGIvdjMvY2xpZW50L2ludGVybmFsL1Jlc3RDbGllbnQuamF2YQ==) | 90.90% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/pull/230?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #230      +/-   ##
==========================================
+ Coverage   89.71%   90.05%   +0.33%     
==========================================
  Files          18       18              
  Lines         963     1036      +73     
  Branches      150      164      +14     
==========================================
+ Hits          864      933      +69     
- Misses         39       40       +1     
- Partials       60       63       +3     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/InfluxCommunity/influxdb3-java/pull/230?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=InfluxCommunity).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: NguyenHoangSon96
created_at: 2025-03-12T11:09:03Z
body: Hi @bednar 
Removed the test cases made codecove low
I will make it up after add Envoy to CI pipline

author_association: CONTRIBUTOR
user: login: jansimonb
created_at: 2025-03-13T13:43:19Z
body: @NguyenHoangSon96 , once all issues are resolved the code coverage should be also increased.
author_association: CONTRIBUTOR
user: login: NguyenHoangSon96
created_at: 2025-03-20T10:23:33Z
body: Hello @jansimonb  and @karel-rehor 
I edited code like you guys suggested, please check again
Some complaints about lack of test cases but I think we could skip these lines because it is not very important. Please tell me if you think otherwise.
Look like that authenticator property in ClientConfig is used to authenticating for the proxy, but it does not work when we have authentication for the proxy and influxdb server. Should we remove it? And let client set up Proxy-Authorization by their own.
author_association: CONTRIBUTOR
user: login: NguyenHoangSon96
created_at: 2025-03-24T07:33:07Z
body: Hi @karel-rehor @jansimonb
I updated the code
Please check when you have time
I did a lot of mistakes. Thank you for your patient 🙏
author_association: CONTRIBUTOR
user: login: NguyenHoangSon96
created_at: 2025-03-24T10:54:54Z
body: Hi @jansimonb 
Thanks
I have refactored like you suggested
Regards testing, I'm not  sure how I can test FlightSqlClient.java more, the large red part in the codecov report
it was copied from FlightClient class in arrow flight and I don't know how to test it to be honest
<img width="1440" alt="Screenshot 2025-03-24 at 17 47 31" src="https://github.com/user-attachments/assets/0443c7e4-ec91-4adc-89b4-0b5c5ceb61a7" />

author_association: CONTRIBUTOR
user: login: jansimonb
created_at: 2025-03-24T12:00:44Z
body: Regarding the code coverage: what about just adding a test which creates a `FlightSqlClient` with host `grpc+unix:///tmp/dummy.sock`? And check whether the constructor passes.
author_association: CONTRIBUTOR
user: login: NguyenHoangSon96
created_at: 2025-03-25T02:20:44Z
body: > Regarding the code coverage: what about just adding a test which creates a `FlightSqlClient` with host `grpc+unix:///tmp/dummy.sock`? And check whether the constructor passes.

Hi Jan
So do we support users passing the host name with schemas "grpc", "grpc+tcp" ,"grpc+unix" ,"grpc+tls"?
Or we will throw an exception
author_association: CONTRIBUTOR
user: login: jansimonb
created_at: 2025-03-25T07:46:36Z
body: > Hi Jan
> So do we support users passing the host name with schemas "grpc", "grpc+tcp" ,"grpc+unix" ,"grpc+tls"?
> Or we will throw an exception

Currently only 2 schemes are used: `grpc+tcp` and `grpc+tls`. This is defined in the [createLocation](https://github.com/InfluxCommunity/influxdb3-java/blob/main/src/main/java/com/influxdb/v3/client/internal/FlightSqlClient.java#L141-L152) method. So basically you could simplify the code:
- you can complete remove the `validSchemas` constant, as we know that `createLocation` will always return these 2 types
- remove the whole `setChannelTypeAndEventLoop` function

That should also fix the code coverage issue.
author_association: CONTRIBUTOR