diff --git a/docs/reference/release-highlights.md b/docs/reference/release-highlights.md
index 645fcf83e..b39ed9c2f 100644
--- a/docs/reference/release-highlights.md
+++ b/docs/reference/release-highlights.md
@@ -9,3 +9,6 @@ These are the important new features and changes in minor releases. Every releas
For a list of detailed changes, including bug fixes, please see the [GitHub project realease notes](https://github.com/elastic/elasticsearch-java/releases).
+### 9.0.0 [release-highlights-900]
+
+[Release Notes](/release-notes/9-0-0.md)
diff --git a/docs/release-notes/9-0-0.md b/docs/release-notes/9-0-0.md
new file mode 100644
index 000000000..745426c92
--- /dev/null
+++ b/docs/release-notes/9-0-0.md
@@ -0,0 +1,433 @@
+---
+navigation_title: "9.0.0"
+---
+# Elasticsearch Java Client 9.0.0 [elasticsearch-java-client-900]
+
+Discover what changed in the 9.0.0 version of the java client.
+
+### Breaking Changes [elasticsearch-java-client-900-breaking-changes]
+
+::::{dropdown} Server accurate aggregation number values
+In previous versions of the client, when the server returned number that was both always present and could be `null` (for example Aggregation results), the client would default to `0` and deserialize the number into a primitive data type instead of an Object. This design choice was changed in this version, changing the fields type to support `null` and be coherent with the server response.
+
+For more information, check the [relevant issue](https://github.com/elastic/elasticsearch-java/issues/843).
+
+**Impact**
The following classes are affected:
+- **elasticsearch._types.aggregations.ArrayPercentilesItem**
+ - `value`: modified from `double` to `Double`, now optional
+- **elasticsearch._types.aggregations.ExtendedStatsAggregate**
+ - `stdDeviation`: modified from `double` to `Double`, now optional
+ - `stdDeviationPopulation`: modified from `double` to `Double`, now optional
+ - `stdDeviationSampling`: modified from `double` to `Double`, now optional
+ - `sumOfSquares`: modified from `double` to `Double`, now optional
+ - `variance`: modified from `double` to `Double`, now optional
+ - `variancePopulation`: modified from `double` to `Double`, now optional
+ - `varianceSampling`: modified from `double` to `Double`, now optional
+- **elasticsearch._types.aggregations.SingleMetricAggregateBase**
+ - `value`: modified from `double` to `Double`, now optional
+- **elasticsearch._types.aggregations.StandardDeviationBounds**
+ - `lower`: modified from `double` to `Double`, now optional
+ - `lowerPopulation`: modified from `double` to `Double`, now optional
+ - `lowerSampling`: modified from `double` to `Double`, now optional
+ - `upper`: modified from `double` to `Double`, now optional
+ - `upperPopulation`: modified from `double` to `Double`, now optional
+ - `upperSampling`: modified from `double` to `Double`, now optional
+- **elasticsearch._types.aggregations.StatsAggregate**
+ - `avg`: modified from `double` to `Double`, now optional
+ - `max`: modified from `double` to `Double`, now optional
+ - `min`: modified from `double` to `Double`, now optional
+- **elasticsearch._types.aggregations.StringStatsAggregate**
+ - `avgLength`: modified from `double` to `Double`, now optional
+ - `entropy`: modified from `double` to `Double`, now optional
+ - `maxLength`: modified from `int` to `Integer`, now optional
+ - `minLength`: modified from `int` to `Integer`, now optional
+- **elasticsearch._types.aggregations.TTestAggregate**
+ - `value`: modified from `double` to `Double`, now optional
+
+**Action**
Steps for mitigating deprecation impact:
+- Make sure to handle the possible `null` value correctly to avoid NullPointerException
+- Remove any workaround that was [previously suggested](https://discuss.elastic.co/t/java-api-client-single-metric-aggregation-zero-or-null-deserializer/356207) to understand whether the `0` returned was actually `0` or `null`
+
+::::
+
+::::{dropdown} Script builder update
+The `Script` class used to only support inline string scripts, now it can also support other formats like `mustache`.
+
+For more information, check the [relevant issue](https://github.com/elastic/elasticsearch-java/issues/876).
+
+**Impact**
The following classes are affected:
+- **elasticsearch._types.Script**
+ - `source`: modified from `String` to `elasticsearch._types.ScriptSource`
+- **elasticsearch._types.ScriptTransform**
+ - `source`: modified from `String` to `elasticsearch._types.ScriptSource`
+- **elasticsearch._types.StoredScript**
+ - `source`: modified from `String` to `elasticsearch._types.ScriptSource`
+- **elasticsearch.core.msearch_template.TemplateConfig**
+ - `source`: modified from `String` to `elasticsearch._types.ScriptSource`
+- **elasticsearch.core.RenderSearchTemplateRequest**
+ - `source`: modified from `String` to `elasticsearch._types.ScriptSource`
+- **elasticsearch.core.search.PhraseSuggestCollateQuery**
+ - `source`: modified from `String` to `elasticsearch._types.ScriptSource`
+- **elasticsearch.core.SearchTemplateRequest**
+ - `source`: modified from `String` to `elasticsearch._types.ScriptSource`
+- **elasticsearch.ingest.ScriptProcessor**
+ - `source`: modified from `String` to `elasticsearch._types.ScriptSource`
+- **elasticsearch.watcher.ScriptCondition**
+ - `source`: modified from `String` to `elasticsearch._types.ScriptSource`
+- **elasticsearch.ingest.ProcessorBase**
+ - `if_`: modified from `String` to `elasticsearch._types.Script`
+
+**Action**
Since the `Script` builder now supports two variants (string and object), the builder has to be updated to specify the type.
+Example with `PutScript`:
+- Old string script
+ ```java
+ esClient.putScript(p -> p
+ .id("my-script")
+ .script(s -> s
+ .lang("painless")
+ .source("Math.log(_score * 2) + params['my_modifier']")
+ )
+ );
+ ```
+- New string script
+ ```java
+ esClient.putScript(p -> p
+ .id("my-script")
+ .script(s -> s
+ .lang("painless")
+ .source(so -> so
+ .scriptString("Math.log(_score * 2) + params['my_modifier']")
+ )
+ )
+ );
+ ```
+- New object script
+ ```java
+ esClient.putScript(p -> p
+ .id("my-script")
+ .script(s -> s
+ .lang("mustache")
+ .source(so -> so
+ .scriptTemplate(st -> st
+ .query(q -> q
+ .match(m -> m
+ .field("message")
+ .query("{{query_string}}")
+ )
+ )
+ )
+ )
+ )
+ );
+ ```
+::::
+
+::::{dropdown} Support for include_named_queries_score
+`include_named_queries_score` is a query parameter that can be enabled for `SearchRequest` and changes the type of `matched_queries` in `SearchResponse.hits.hits` from List to Map. Previous versions of the client didn't support the options, since the json deserialize couldn't know which response type to expect. To support the feature, new versions of the client will always treat `matched_queries` as a Map, where the values are null in case the original field was returned as a list.
+
+For more information, check the [relevant issue](https://github.com/elastic/elasticsearch-java/issues/634).
+
+**Impact**
The following classes are affected:
+- **elasticsearch.core.search.Hit**
+ - `matchedQueries`: modified from `List` to `Map`
+
+**Action**
Reading `matched_queries` will be different since it's now a Map, so instead of iterating on the List values now the Map's KeySet has to be iterated to obtain the same result as before.
+
+::::
+
+::::{dropdown} Package change for EsqlFormat
+Breaking change caused by refactoring the ESQL package.
+
+**Impact**
The following classes are affected:
+- **elasticsearch.esql.QueryRequest**
+ - `format`: modified from `elasticsearch.esql.query.EsqlFormat` to `elasticsearch.esql.EsqlFormat`
+
+**Action**
Change the import from `elasticsearch.esql.query.EsqlFormat` to `elasticsearch.esql.EsqlFormat`
+
+::::
+
+::::{dropdown} Body name and getter change for Responses
+Response types should have had a specific body name and getter matching the body type, but in previous versions of the client it was just called `valueBody`.
+
+**Impact**
The following classes are affected:
+- **elasticsearch.ssl.CertificatesResponse**
+ - `valueBody` is now `certificates`
+- **elasticsearch.snapshot.RepositoryVerifyIntegrityResponse**
+ - `valueBody` is now `result`
+- **elasticsearch.snapshot.GetRepositoryResponse**
+ - `valueBody` is now `repositories`
+- **elasticsearch.inference.TextEmbeddingResponse**
+ - `valueBody` is now `inferenceResult`
+- **elasticsearch.cluster.StateResponse**
+ - `valueBody` is now `state`
+- **elasticsearch.cat.TransformsResponse**
+ - `valueBody` is now `transforms`
+- **elasticsearch.cat.ThreadPoolResponse**
+ - `valueBody` is now `threadPools`
+- **elasticsearch.cat.TemplatesResponse**
+ - `valueBody` is now `templates`
+- **elasticsearch.cat.TasksResponse**
+ - `valueBody` is now `tasks`
+- **elasticsearch.cat.SnapshotsResponse**
+ - `valueBody` is now `snapshots`
+- **elasticsearch.cat.ShardsResponse**
+ - `valueBody` is now `shards`
+- **elasticsearch.cat.SegmentsResponse**
+ - `valueBody` is now `segments`
+- **elasticsearch.cat.RepositoriesResponse**
+ - `valueBody` is now `repositories`
+- **elasticsearch.cat.RecoveryResponse**
+ - `valueBody` is now `recoveryRecords`
+- **elasticsearch.cat.PluginsResponse**
+ - `valueBody` is now `plugins`
+- **elasticsearch.cat.PendingTasksResponse**
+ - `valueBody` is now `pendingTasks`
+- **elasticsearch.cat.NodesResponse**
+ - `valueBody` is now `nodes`
+- **elasticsearch.cat.NodeattrsResponse**
+ - `valueBody` is now `nodeAttributes`
+- **elasticsearch.cat.MlTrainedModelsResponse**
+ - `valueBody` is now `trainedModels`
+- **elasticsearch.cat.MlJobsResponse**
+ - `valueBody` is now `jobs`
+- **elasticsearch.cat.MlDataFrameAnalyticsResponse**
+ - `valueBody` is now `dataFrameAnalytics`
+- **elasticsearch.cat.MlDatafeedsResponse**
+ - `valueBody` is now `datafeeds`
+- **elasticsearch.cat.MasterResponse**
+ - `valueBody` is now `masters`
+- **elasticsearch.cat.IndicesResponse**
+ - `valueBody` is now `indices`
+- **elasticsearch.cat.HealthResponse**
+ - `valueBody` is now `healthRecords`
+- **elasticsearch.cat.FielddataResponse**
+ - `valueBody` is now `fielddataRecords`
+- **elasticsearch.cat.CountResponse**
+ - `valueBody` is now `countRecords`
+- **elasticsearch.cat.ComponentTemplatesResponse**
+ - `valueBody` is now `componentTemplates`
+- **elasticsearch.cat.AllocationResponse**
+ - `valueBody` is now `allocations`
+- **elasticsearch.cat.AliasesResponse**
+ - `valueBody` is now `aliases`
+
+**Action**
Replace the `valueBody()` getter with the specific getter depending on the class used.
+
+::::
+
+::::{dropdown} Map to NamedValue indicesBoost, dynamicTemplates
+`indicesBoost` and `dynamicTemplates` were wrongly mapped as `List