public CompletableFuture<InsertResponse> insert(String tableName, List<?> data, InsertSettings settings) {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("Data cannot be empty");
        }


        String operationId = registerOperationMetrics();
        settings.setOperationId(operationId);
        globalClientStats.get(operationId).start(ClientMetrics.OP_DURATION);
        globalClientStats.get(operationId).start(ClientMetrics.OP_SERIALIZATION);

        //Add format to the settings
        if (settings == null) {
            settings = new InsertSettings();
        }

        boolean hasDefaults = this.tableSchemaHasDefaults.get(tableName);
        ClickHouseFormat format = hasDefaults? ClickHouseFormat.RowBinaryWithDefaults : ClickHouseFormat.RowBinary;
        TableSchema tableSchema = tableSchemaCache.get(tableName);
        if (tableSchema == null) {
            throw new IllegalArgumentException("Table schema not found for table: " + tableName + ". Did you forget to register it?");
        }
        //Lookup the Serializer for the POJO
        Map<String, POJOFieldSerializer> classSerializers = pojoSerDe.getFieldSerializers(data.get(0).getClass(),
                tableSchema);
        List<POJOFieldSerializer> serializersForTable = new ArrayList<>();
        for (ClickHouseColumn column : tableSchema.getColumns()) {
            if (column.hasDefault() && column.getDefaultValue() != ClickHouseColumn.DefaultValue.DEFAULT ) {
                continue;
            }
            POJOFieldSerializer serializer = classSerializers.get(column.getColumnName());
            if (serializer == null) {
                throw new IllegalArgumentException("No serializer found for column '" + column.getColumnName() + "'. Did you forget to register it?");
            }
            serializersForTable.add(serializer);
        }


        String retry = configuration.get(ClientConfigProperties.RETRY_ON_FAILURE.getKey());
        final int maxRetries = retry == null ? 0 : Integer.parseInt(retry);

        settings.setOption(ClientConfigProperties.INPUT_OUTPUT_FORMAT.getKey(), format.name());
        final InsertSettings finalSettings = settings;
        Supplier<InsertResponse> supplier = () -> {
            long startTime = System.nanoTime();
            // Selecting some node
            Endpoint selectedEndpoint = getNextAliveNode();

            RuntimeException lastException = null;
            for (int i = 0; i <= maxRetries; i++) {
                // Execute request
                try (ClassicHttpResponse httpResponse =
                        httpClientHelper.executeRequest(selectedEndpoint, finalSettings.getAllSettings(), lz4Factory,
                                out -> {
                                    out.write("INSERT INTO ".getBytes());
                                    out.write(tableName.getBytes());
                                    out.write(" \n FORMAT ".getBytes());
                                    out.write(format.name().getBytes());
                                    out.write(" \n".getBytes());
                                    for (Object obj : data) {

                                        for (POJOFieldSerializer serializer : serializersForTable) {
                                            try {
                                                serializer.serialize(obj, out);
                                            } catch (InvocationTargetException | IllegalAccessException | IOException e) {
                                                throw new DataSerializationException(obj, serializer, e);
                                            }
                                        }
                                    }
                                    out.close();
                                })) {


                    // Check response
                    if (httpResponse.getCode() == HttpStatus.SC_SERVICE_UNAVAILABLE) {
                        LOG.warn("Failed to get response. Server returned {}. Retrying. (Duration: {})", httpResponse.getCode(), System.nanoTime() - startTime);
                        selectedEndpoint = getNextAliveNode();
                        continue;
                    }

                    ClientStatisticsHolder clientStats = globalClientStats.remove(operationId);
                    OperationMetrics metrics = new OperationMetrics(clientStats);
                    String summary = HttpAPIClientHelper.getHeaderVal(httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_SRV_SUMMARY), "{}");
                    ProcessParser.parseSummary(summary, metrics);
                    String queryId =  HttpAPIClientHelper.getHeaderVal(httpResponse.getFirstHeader(ClickHouseHttpProto.HEADER_QUERY_ID), finalSettings.getQueryId(), String::valueOf);
                    metrics.operationComplete();
                    metrics.setQueryId(queryId);
                    return new InsertResponse(metrics);
                } catch (Exception e) {
                    lastException = httpClientHelper.wrapException(String.format("Query request failed (Attempt: %s/%s - Duration: %s)",
                            (i + 1), (maxRetries + 1), System.nanoTime() - startTime), e);
                    if (httpClientHelper.shouldRetry(e, finalSettings.getAllSettings())) {
                        LOG.warn("Retrying.", e);
                        selectedEndpoint = getNextAliveNode();
                    } else {
                        throw lastException;
                    }
                }
            }
            throw new ClientException("Insert request failed after attempts: " + (maxRetries + 1) + " - Duration: " + (System.nanoTime() - startTime), lastException);
        };

        return runAsyncOperation(supplier, settings.getAllSettings());

    }
<p>Sends write request to database. List of objects is converted into a most suitable format
then it is sent to a server. Members of the list must be pre-registered using
{@link #register(Class, TableSchema)} method:</p>

<pre>{@code
client.register(SamplePOJO.class, tableSchema);
List<Object> input = new ArrayList<>();
// ... Insert some items into input list
Future<InsertResponse> response = client.insert(tableName, simplePOJOs, settings);
}
</pre>

@param tableName - destination table name
@param data  - data stream to insert
@param settings - insert operation settings
@throws IllegalArgumentException when data is empty or not registered
@return {@code CompletableFuture<InsertResponse>} - a promise to insert response
client-v2/src/main/java/com/clickhouse/client/api/Client.java