elasticsearch-java
elasticsearch-java copied to clipboard
Bulk indexing fails with INDENT_OUTPUT configured on JacksonJsonpMapper's ObjectMapper
Java API client version
8.9.0
Java version
17
Elasticsearch Version
8.9.0
Problem description
Was trying to enable SerializationFeature.INDENT_OUTPUT
with JacksonJsonpMapper's ObjectMapper in order to make traces easier to read during local testing and debugging. Unfortunately, this seems to cause the ES client to generate a malformed request:
20:31:52.690 [main] TRACE tracer - curl -iX POST 'https://localhost:52608/_bulk' -d '{"index":{"_index":"test"}}
{
"name" : "book",
"description" : "a small book"
}
'
# HTTP/1.1 400 Bad Request
# X-elastic-product: Elasticsearch
# content-type: application/vnd.elasticsearch+json;compatible-with=8
# content-length: 301
#
# {"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING]"}],"type":"illegal_argument_exception","reason":"Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING]"},"status":400}
Reproducer:
package com.yammer.embeddings.elastic;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.utility.DockerImageName;
class BulkBugTest {
static final ElasticsearchContainer container = new ElasticsearchContainer(
DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch")
.withTag("8.9.0"));
static final String INDEX_NAME = "test";
static RestClient restClient;
record Item(String name, String description) {}
@BeforeAll
static void init() throws Exception {
container.start();
var credentials = new BasicCredentialsProvider();
credentials.setCredentials(
AuthScope.ANY, new UsernamePasswordCredentials(
"elastic", ElasticsearchContainer.ELASTICSEARCH_DEFAULT_PASSWORD)
);
restClient = RestClient.builder(HttpHost.create("https://" + container.getHttpHostAddress()))
.setHttpClientConfigCallback(builder -> builder
.setSSLContext(container.createSslContextFromCa())
.setDefaultCredentialsProvider(credentials))
.build();
var client = new ElasticsearchClient(
new RestClientTransport(restClient, new JacksonJsonpMapper()));
client.indices().create(req -> req
.index(INDEX_NAME)
.mappings(mappings -> mappings
.properties("name", b -> b.keyword(k -> k.index(true)))
.properties("description", b -> b.keyword(k -> k.index(false)))
)
);
}
@AfterAll
static void destroy() throws Exception {
if (restClient != null) {
restClient.close();
}
container.stop();
}
@Test
void succeedsWithoutIndent() throws Exception {
var client = new ElasticsearchClient(
new RestClientTransport(restClient, new JacksonJsonpMapper()));
var item = new Item("book", "a small book");
var bulk = new BulkRequest.Builder();
bulk.operations(op -> op.index(index -> index.index(INDEX_NAME).document(item)));
var response = client.bulk(bulk.build());
}
@Test
void failsWithIndent() throws Exception {
var mapper = new ObjectMapper()
.configure(SerializationFeature.INDENT_OUTPUT, true);
var client = new ElasticsearchClient(
new RestClientTransport(restClient, new JacksonJsonpMapper(mapper)));
var item = new Item("book", "a small book");
var bulk = new BulkRequest.Builder();
bulk.operations(op -> op.index(index -> index.index(INDEX_NAME).document(item)));
var response = client.bulk(bulk.build());
}
}