Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
### Features

1. [#360](https://github.com/InfluxCommunity/influxdb3-java/pull/360): Support passing interceptors to the Flight client.
1. [#363](https://github.com/InfluxCommunity/influxdb3-java/pull/363): Support custom tag order via `tagOrder` write option.
See [Sort tags by priority](https://docs.influxdata.com/influxdb3/enterprise/write-data/best-practices/schema-design/#sort-tags-by-query-priority) for more.

## 1.8.0 [2026-02-19]

Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,13 @@ To start with the client, import the `com.influxdb.v3.client` package and create
package com.influxdb.v3;

import java.time.Instant;
import java.util.List;
import java.util.stream.Stream;

import com.influxdb.v3.client.InfluxDBClient;
import com.influxdb.v3.client.query.QueryOptions;
import com.influxdb.v3.client.Point;
import com.influxdb.v3.client.write.WriteOptions;

public class IOxExample {
public static void main(String[] args) throws Exception {
Expand All @@ -100,6 +102,17 @@ Point point = Point.measurement("temperature")
.setTimestamp(Instant.now().minusSeconds(-10));
client.writePoint(point);

WriteOptions orderedTagWrite = new WriteOptions.Builder()
.tagOrder(List.of("region", "host"))
.build();
client.writePoint(
Point.measurement("temperature")
.setTag("host", "server-1")
.setTag("region", "eu-west")
.setField("value", 60.25),
orderedTagWrite
);

//
// Write by LineProtocol
//
Expand Down
81 changes: 69 additions & 12 deletions src/main/java/com/influxdb/v3/client/Point.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@
import java.math.BigInteger;
import java.text.NumberFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -530,7 +534,7 @@ public Point copy() {
*/
@Nonnull
public String toLineProtocol() {
return toLineProtocol(null);
return toLineProtocol(null, null, null);
}

/**
Expand All @@ -541,11 +545,26 @@ public String toLineProtocol() {
*/
@Nonnull
public String toLineProtocol(@Nullable final WritePrecision precision) {
return toLineProtocol(precision, null, null);
}

/**
* Transform to Line Protocol.
*
* @param precision required precision
* @param defaultTags default tags to include in point serialization
* @param tagOrder preferred order of tags in point serialization
* @return Line Protocol
*/
@Nonnull
public String toLineProtocol(@Nullable final WritePrecision precision,
@Nullable final Map<String, String> defaultTags,
@Nullable final List<String> tagOrder) {

StringBuilder sb = new StringBuilder();

escapeKey(sb, getMeasurement(), false);
appendTags(sb);
appendTags(sb, defaultTags, tagOrder);
boolean appendedFields = appendFields(sb);
if (!appendedFields) {
return "";
Expand All @@ -564,24 +583,62 @@ private Point putField(@Nonnull final String field, @Nullable final Object value
return this;
}

private void appendTags(@Nonnull final StringBuilder sb) {

for (String name : values.getTagNames()) {
private void appendTags(@Nonnull final StringBuilder sb,
@Nullable final Map<String, String> defaultTags,
@Nullable final List<String> tagOrder) {
if ((defaultTags == null || defaultTags.isEmpty()) && (tagOrder == null || tagOrder.isEmpty())) {
values.forEachTag((name, value) -> appendTag(sb, name, value));
sb.append(' ');
return;
}

String value = values.getTag(name);
Set<String> remaining = new TreeSet<>();
values.forEachTagName(pointTag -> {
if (!pointTag.isEmpty()) {
remaining.add(pointTag);
}
});
if (defaultTags != null) {
for (String defaultTag : defaultTags.keySet()) {
if (defaultTag != null && !defaultTag.isEmpty()) {
remaining.add(defaultTag);
}
}
}

if (name.isEmpty() || value == null || value.isEmpty()) {
continue;
List<String> orderedTagNames = new ArrayList<>();
if (tagOrder != null && !tagOrder.isEmpty()) {
for (String preferredTag : tagOrder) {
if (preferredTag == null || preferredTag.isEmpty()) {
continue;
}
if (remaining.remove(preferredTag)) {
orderedTagNames.add(preferredTag);
}
}
}
orderedTagNames.addAll(remaining);

sb.append(',');
escapeKey(sb, name);
sb.append('=');
escapeKey(sb, value);
for (String name : orderedTagNames) {
String value = values.getTag(name);
if (defaultTags != null && defaultTags.containsKey(name)) {
value = defaultTags.get(name);
}
appendTag(sb, name, value);
}
sb.append(' ');
}

private void appendTag(@Nonnull final StringBuilder sb, @Nullable final String name, @Nullable final String value) {
if (name == null || name.isEmpty() || value == null || value.isEmpty()) {
return;
}
sb.append(',');
escapeKey(sb, name);
sb.append('=');
escapeKey(sb, value);
}

private boolean appendFields(@Nonnull final StringBuilder sb) {

boolean appended = false;
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/influxdb/v3/client/PointValues.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import java.time.Instant;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
Expand Down Expand Up @@ -226,6 +228,16 @@ public String[] getTagNames() {
return tags.keySet().toArray(new String[0]);
}

void forEachTag(@Nonnull final BiConsumer<String, String> consumer) {
Arguments.checkNotNull(consumer, "consumer");
tags.forEach(consumer);
}

void forEachTagName(@Nonnull final Consumer<String> consumer) {
Arguments.checkNotNull(consumer, "consumer");
tags.keySet().forEach(consumer);
}

/**
* Gets the float field value associated with the specified name.
* If the field is not present, returns null.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.zip.GZIPOutputStream;
Expand Down Expand Up @@ -330,21 +329,29 @@ private <T> void writeData(@Nonnull final List<T> data, @Nonnull final WriteOpti
}

Map<String, String> defaultTags = options.defaultTagsSafe(config);
List<String> tagOrder = options.tagOrderSafe();

StringBuilder lineProtocolBuilder = new StringBuilder();
for (T item : data) {
String line;
if (item == null) {
line = null;
} else if (item instanceof Point) {
line = ((Point) item).toLineProtocol(null, defaultTags, tagOrder);
} else {
line = item.toString();
}

if (line == null || line.isEmpty()) {
continue;
}

String lineProtocol = data.stream().map(item -> {
if (item == null) {
return null;
} else if (item instanceof Point) {
for (String key : defaultTags.keySet()) {
((Point) item).setTag(key, defaultTags.get(key));
}
return ((Point) item).toLineProtocol();
} else {
return item.toString();
}
})
.filter(it -> it != null && !it.isEmpty())
.collect(Collectors.joining("\n"));
if (lineProtocolBuilder.length() > 0) {
lineProtocolBuilder.append('\n');
}
lineProtocolBuilder.append(line);
}
String lineProtocol = lineProtocolBuilder.toString();

if (lineProtocol.isEmpty()) {
LOG.warning("No data to write, please check your input data.");
Expand All @@ -353,9 +360,9 @@ private <T> void writeData(@Nonnull final List<T> data, @Nonnull final WriteOpti

Map<String, String> headers = new HashMap<>(Map.of("Content-Type", "text/plain; charset=utf-8"));
byte[] body = lineProtocol.getBytes(StandardCharsets.UTF_8);
if (lineProtocol.length() >= options.gzipThresholdSafe(config)) {
if (body.length >= options.gzipThresholdSafe(config)) {
try {
body = gzipData(lineProtocol.getBytes(StandardCharsets.UTF_8));
body = gzipData(body);
headers.put("Content-Encoding", "gzip");
} catch (IOException e) {
throw new InfluxDBApiException(e);
Expand Down
Loading
Loading