RestHighLevelClient版本必须与ES集群主版本严格一致:7.x用elasticsearch-rest-high-level-client对应版本,8.x改用elasticsearch-java+elasticsearch-core;需配BOM、调大连接池、避免Jackson序列化陷阱、正确解析嵌套字段。

RestHighLevelClient 依赖怎么选对版本
Elasticsearch 7.x 和 8.x 的 RestHighLevelClient 不兼容,用错直接报 NoClassDefFoundError 或 IllegalArgumentException: unknown setting。关键不是看 Java 版本,而是看 ES 集群的主版本号。
- 如果集群是
7.17.0,必须用elasticsearch-rest-high-level-client7.17.0,不能用 8.x 的包 - 如果集群是
8.4.0,要用elasticsearch-java+elasticsearch-core,RestHighLevelClient已被标记为 deprecated,且 8.x 官方不再发布它的新版本 - Maven 里别只写
elasticsearch-rest-high-level-client,得加elasticsearch同版本的 BOM(比如elasticsearch-bom),否则容易因 transitive 依赖混入不同版本的httpasyncclient导致连接复用失效
示例(ES 7.17):
<dependency><groupid>org.elasticsearch.client</groupid><artifactid>elasticsearch-rest-high-level-client</artifactid><version>7.17.0</version></dependency>
初始化 RestHighLevelClient 时的线程与连接陷阱
RestHighLevelClient 是线程安全的,但底层 RestClient 默认只开 2 个并发连接、最多 10 个总连接,压测时立刻卡住,现象是请求 hang 住或抛 java.io.IOException: Connection refused(其实是连接池耗尽,不是真连不上)。
- 必须显式配置
HttpClientConfigCallback,调大maxConnPerRoute和maxConnTotal - 别在每次请求都 new 一个 client,它重量级,应单例持有并手动 close(通常在 Spring 容器 shutdown 时)
- 如果用 Spring Boot,别依赖
spring-boot-starter-data-elasticsearch自带的 autoconfig——它默认不配连接池,且 3.x 起已切到elasticsearch-java
常见修复写法:
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200))
.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder.setDefaultIOReactorConfig(
IOReactorConfig.custom().setIoThreadCount(4).build())
.setMaxConnPerRoute(20)
.setMaxConnTotal(100)
)
);
IndexRequest 和 BulkRequest 的 JSON 序列化坑
RestHighLevelClient 不自动处理 POJO 到 JSON 的序列化,你传 Map 或 String 进去,它就原样发;如果传自定义对象,会走 Jackson,但默认没注册 LocalDateTime 等类型处理器,结果报 JsonProcessingException。
- 写文档前,优先用
XContentType.JSON+String,避免 Jackson 干预:request.source("{\"name\":\"foo\"}", XContentType.JSON) - 如果非要用对象,得自己配
ObjectMapper并传给RestClient构造器,或者用Map手动拼结构(更可控) -
BulkRequest里混用IndexRequest和UpdateRequest时,注意UpdateRequest.doc()传的是 partial doc,不是完整 JSON 字符串,漏掉.docAsUpsert(true)可能静默失败
典型错误:
// ❌ 错误:没指定 XContentType,client 会尝试用默认序列化器,可能失败 request.source(myObj); <p>// ✅ 正确:明确内容类型,避开 Jackson 配置问题 request.source(objectMapper.writeValueAsString(myObj), XContentType.JSON);</p>
搜索结果解析时字段名和嵌套结构对不上
ES 返回的 SearchResponse 里,getHits().getHits() 拿到的是 SearchHit,它的 sourceAsString() 是原始 JSON 字符串,getSourceAsMap() 是解析后的 Map。但 Map 里的 key 是字符串,嵌套字段如 user.name 不会自动转成嵌套 Map,而是平铺成 key "user.name"。
- 别假设
map.get("user").get("name")能取到值——实际得map.get("user.name") - 如果用了
dynamic: strict或字段是nested类型,getSourceAsMap()会丢掉类型信息,建议用sourceRef()+new String(sourceRef.bytes, sourceRef.offset, sourceRef.length)拿原始字节再解析 - 高亮字段在
getHighlightFields()里,不在source中,别漏掉单独处理
快速验证字段路径:
for (SearchHit hit : response.getHits()) {
Map<string object> source = hit.getSourceAsMap();
// ✅ 正确访问扁平 key
String name = (String) source.get("user.name");
// ❌ 下面这行大概率是 null
// String name = ((Map) source.get("user")).get("name");
}</string>
ES 的 mapping 定义和客户端解析逻辑是两层皮,字段名写错、类型设成 keyword 却当 text 用、嵌套没开 include_in_root——这些都会让 getSourceAsMap() 返回意料之外的结构。调试时先打全 hit.getSourceAsString(),比猜 key 更可靠。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











