Spring Boot 3.1.4集成Elasticsearch存文档时HTTP方法错误求助
问题背景
将Spring Boot Elastic应用从2.x版本升级至3.1.4版本时,持久化文档出现错误,相同代码在Spring Boot 2.7+Elasticsearch 7.11环境下可正常运行。已通过新项目复现该问题。
错误信息
Caused by: co.elastic.clients.elasticsearch._types.ElasticsearchException: [es/index] failed: [null] Incorrect HTTP method for uri [/vaultdoc/_doc/?refresh=false] and method [PUT], allowed: [POST]
堆栈跟踪
Caused by: co.elastic.clients.elasticsearch._types.ElasticsearchException: [es/index] failed: [null] Incorrect HTTP method for uri [/vaultdoc/_doc/?refresh=false] and method [PUT], allowed: [POST] at co.elastic.clients.transport.rest_client.RestClientTransport.getHighLevelResponse(RestClientTransport.java:334) at co.elastic.clients.transport.rest_client.RestClientTransport.performRequest(RestClientTransport.java:154) at co.elastic.clients.elasticsearch.ElasticsearchClient.index(ElasticsearchClient.java:1116) at org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate.lambda$doIndex$6(ElasticsearchTemplate.java:220) at org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate.execute(ElasticsearchTemplate.java:623)
相关代码
测试代码(Kotlin)
@Testcontainers @SpringBootTest(classes = [TestApp::class], properties = ["logging.level.org.testcontainers=debug"]) @ContextConfiguration(initializers = [SetupContainers::class]) class VaultDocumentTest(@Autowired private val vaultDocumentRepo: VaultDocumentRepo) { @Test fun simpleTest() { println("Count Call Works-->${vaultDocumentRepo.count()}")//this works - returns 0 println("findAll Works -->${vaultDocumentRepo.findAll().toList().size}") //this works - returns 0 vaultDocumentRepo.save(VaultDocument("Test101")) //save fails with error '[es/index] failed: [null] Incorrect HTTP method for uri [/vaultdoc/_doc/?refresh=false] and method [PUT], allowed: [POST]' } }
文档实体
@Document(indexName = "vaultdoc") class VaultDocument( @Field(type = FieldType.Text, store = true, fielddata = true) val vaultDocumentName: String, @org.springframework.data.annotation.Id var id: String = "", )
Repository接口
@Repository interface VaultDocumentRepo : ElasticsearchRepository<VaultDocument, String> { fun findByVaultDocumentName(vaultDocumentName: String): VaultDocument? }
Testcontainers配置
class SetupContainers : ApplicationContextInitializer<ConfigurableApplicationContext> { override fun initialize(applicationContext: ConfigurableApplicationContext) { elasticsearchContainer.withEnv( mapOf( "xpack.security.enabled" to "false", "xpack.security.http.ssl.enabled" to "false", "action.destructive_requires_name" to "false", "reindex.remote.whitelist" to "localhost:9200" ) ) elasticsearchContainer.start() TestPropertyValues.of( "spring.elasticsearch.uris=http://${elasticsearchContainer.httpHostAddress}", ).applyTo(applicationContext.environment) } companion object { @Container val elasticsearchContainer: ElasticsearchContainer = ElasticsearchContainer( DockerImageName .parse("docker.elastic.co/elasticsearch/elasticsearch") .withTag("8.10.0") ) } }
问题原因及解决方案
原因分析
问题出在实体类的id字段默认值为空字符串:
@org.springframework.data.annotation.Id var id: String = "",
在Spring Data Elasticsearch 3.x(对应Spring Boot 3.x)中,当id为空字符串时,框架会尝试用PUT请求发送到/_doc/路径,但Elasticsearch 8.x要求创建无指定ID的文档时必须用POST请求,PUT仅允许在指定具体ID时使用。而Spring Boot 2.7对应的Spring Data Elasticsearch版本处理逻辑不同,空ID会自动触发POST请求生成ID。
解决方案
有两种可行的解决方式:
- 移除id的默认空字符串赋值:让id字段为null,这样Spring Data Elasticsearch会识别为需要自动生成ID,发送POST请求:
@org.springframework.data.annotation.Id var id: String? = null,
- 显式配置索引请求使用POST:通过自定义
ElasticsearchTemplate或修改保存逻辑,确保无ID时使用POST方法。不过第一种方式更简洁,符合Spring Data的默认行为预期。
验证修改后,vaultDocumentRepo.save(VaultDocument("Test101"))会自动发送POST请求到/vaultdoc/_doc/?refresh=false,符合Elasticsearch 8.x的要求,即可正常保存文档。
内容的提问来源于stack exchange,提问作者Mannie

