Skip to content

Commit 2a846c9

Browse files
craftmaster2190bclozel
authored andcommitted
Add HttpHeaders.copyOf factory method
Prior to this commit, the `HttpHeaders` class would provide constructor variants where the instances are are backed by the existing headers collection given as a parameter. While such constructors are clearly documented and meant for internal usage, there are cases where developers would like to copy the data from an existing headers instance without being backed by the same collection instance and thus, being mutated from some place else. This commit introduces new factory methods `HttpHeaders.copyOf` for this purpose. While this name aligns with some of the Java collections factory methods, in this case the returned instance is not immutable, on purpose. `HttpHeaders` does not extends `MultiValueMap` anymore and shouldn't be seen as such. Closes: gh-34341 Signed-off-by: Bryce J. Fisher <bryce.fisher@gmail.com> [brian.clozel@broadcom.com: reduce scope and update javadoc] Signed-off-by: Brian Clozel <brian.clozel@broadcom.com>
1 parent 7695be0 commit 2a846c9

File tree

3 files changed

+36
-2
lines changed

3 files changed

+36
-2
lines changed

spring-web/src/main/java/org/springframework/http/HttpHeaders.java

+19
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,25 @@ public HttpHeaders(HttpHeaders httpHeaders) {
491491
}
492492
}
493493

494+
/**
495+
* Create a new {@code HttpHeaders} mutable instance and copy all header values given as a parameter.
496+
* @param headers the headers to copy
497+
* @since 7.0
498+
*/
499+
public static HttpHeaders copyOf(MultiValueMap<String, String> headers) {
500+
HttpHeaders httpHeadersCopy = new HttpHeaders();
501+
headers.forEach((key, values) -> httpHeadersCopy.put(key, new ArrayList<>(values)));
502+
return httpHeadersCopy;
503+
}
504+
505+
/**
506+
* Create a new {@code HttpHeaders} mutable instance and copy all header values given as a parameter.
507+
* @param httpHeaders the headers to copy
508+
* @since 7.0
509+
*/
510+
public static HttpHeaders copyOf(HttpHeaders httpHeaders) {
511+
return copyOf(httpHeaders.headers);
512+
}
494513

495514
/**
496515
* Get the list of header values for the given header name, if any.

spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java

-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ class ReadOnlyHttpHeaders extends HttpHeaders {
4949
@SuppressWarnings("serial")
5050
private @Nullable List<MediaType> cachedAccept;
5151

52-
5352
ReadOnlyHttpHeaders(MultiValueMap<String, String> headers) {
5453
super(headers);
5554
}

spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -77,6 +77,22 @@ void writableHttpHeadersUnwrapsMultiple() {
7777
writeable.setContentType(MediaType.APPLICATION_JSON);
7878
}
7979

80+
@Test
81+
void copyOfCopiesHeaders() {
82+
headers.setContentType(MediaType.APPLICATION_JSON);
83+
headers.add("X-Project", "Spring");
84+
headers.add("X-Project", "Framework");
85+
HttpHeaders readOnly = HttpHeaders.readOnlyHttpHeaders(headers);
86+
assertThat(readOnly.getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
87+
88+
HttpHeaders writable = HttpHeaders.copyOf(readOnly);
89+
writable.setContentType(MediaType.TEXT_PLAIN);
90+
// content-type value is cached by ReadOnlyHttpHeaders
91+
assertThat(readOnly.getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
92+
assertThat(writable.getContentType()).isEqualTo(MediaType.TEXT_PLAIN);
93+
assertThat(writable.get("X-Project")).contains("Spring", "Framework");
94+
}
95+
8096
@Test
8197
void getOrEmpty() {
8298
String key = "FOO";

0 commit comments

Comments
 (0)