diff --git a/src/jdk.httpserver/share/classes/module-info.java b/src/jdk.httpserver/share/classes/module-info.java
index 15e9e2cc36d1e..ac147582b1421 100644
--- a/src/jdk.httpserver/share/classes/module-info.java
+++ b/src/jdk.httpserver/share/classes/module-info.java
@@ -23,6 +23,8 @@
* questions.
*/
+import com.sun.net.httpserver.*;
+
/**
* Defines the JDK-specific HTTP server API, and provides the jwebserver tool
* for running a minimal HTTP server.
@@ -109,6 +111,14 @@
* and implementation of the server does not intend to be a full-featured, high performance
* HTTP server.
*
+ * @implNote
+ * Prior to JDK 26, in the JDK default implementation, the {@link HttpExchange} attribute map was
+ * shared with the enclosing {@link HttpContext}.
+ * Since JDK 26, by default, exchange attributes are per-exchange and the context attributes must
+ * be accessed by calling {@link HttpExchange#getHttpContext() getHttpContext()}{@link
+ * HttpContext#getAttributes() .getAttributes()}.
+ * A new system property, {@systemProperty jdk.httpserver.attributes} (default value: {@code ""})
+ * allows to revert this new behavior. Set this property to "context" to restore the pre JDK 26 behavior.
* @toolGuide jwebserver
*
* @uses com.sun.net.httpserver.spi.HttpServerProvider
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java
index 1119d1e386b3e..0899952b4950f 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,7 @@
import java.net.*;
import javax.net.ssl.*;
import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.text.*;
@@ -59,6 +60,9 @@ class ExchangeImpl {
/* for formatting the Date: header */
private static final DateTimeFormatter FORMATTER;
+ private static final boolean perExchangeAttributes =
+ !System.getProperty("jdk.httpserver.attributes", "")
+ .equals("context");
static {
String pattern = "EEE, dd MMM yyyy HH:mm:ss zzz";
FORMATTER = DateTimeFormatter.ofPattern(pattern, Locale.US)
@@ -76,7 +80,7 @@ class ExchangeImpl {
PlaceholderOutputStream uos_orig;
boolean sentHeaders; /* true after response headers sent */
- Map attributes;
+ final Map attributes;
int rcode = -1;
HttpPrincipal principal;
ServerImpl server;
@@ -91,6 +95,9 @@ class ExchangeImpl {
this.uri = u;
this.connection = connection;
this.reqContentLen = len;
+ this.attributes = perExchangeAttributes
+ ? new ConcurrentHashMap<>()
+ : getHttpContext().getAttributes();
/* ros only used for headers, body written directly to stream */
this.ros = req.outputStream();
this.ris = req.inputStream();
@@ -361,26 +368,15 @@ public SSLSession getSSLSession () {
}
public Object getAttribute (String name) {
- if (name == null) {
- throw new NullPointerException ("null name parameter");
- }
- if (attributes == null) {
- attributes = getHttpContext().getAttributes();
- }
- return attributes.get (name);
+ return attributes.get(Objects.requireNonNull(name, "null name parameter"));
}
public void setAttribute (String name, Object value) {
- if (name == null) {
- throw new NullPointerException ("null name parameter");
- }
- if (attributes == null) {
- attributes = getHttpContext().getAttributes();
- }
+ var key = Objects.requireNonNull(name, "null name parameter");
if (value != null) {
- attributes.put (name, value);
+ attributes.put(key, value);
} else {
- attributes.remove (name);
+ attributes.remove(key);
}
}
diff --git a/test/jdk/com/sun/net/httpserver/ExchangeAttributeTest.java b/test/jdk/com/sun/net/httpserver/ExchangeAttributeTest.java
index 2ce3dfd016d00..e7bea2814db03 100644
--- a/test/jdk/com/sun/net/httpserver/ExchangeAttributeTest.java
+++ b/test/jdk/com/sun/net/httpserver/ExchangeAttributeTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,9 @@
* @summary Tests for HttpExchange set/getAttribute
* @library /test/lib
* @run junit/othervm ExchangeAttributeTest
+ * @run junit/othervm -Djdk.httpserver.attributes=context ExchangeAttributeTest
+ * @run junit/othervm -Djdk.httpserver.attributes=random-string ExchangeAttributeTest
+ * @run junit/othervm -Djdk.httpserver.attributes ExchangeAttributeTest
*/
import com.sun.net.httpserver.HttpExchange;
@@ -71,7 +74,7 @@ public static void setup() {
public void testExchangeAttributes() throws Exception {
var handler = new AttribHandler();
var server = HttpServer.create(new InetSocketAddress(LOOPBACK_ADDR,0), 10);
- server.createContext("/", handler);
+ server.createContext("/", handler).getAttributes().put("attr", "context-val");
server.start();
try {
var client = HttpClient.newBuilder().proxy(NO_PROXY).build();
@@ -101,8 +104,17 @@ static class AttribHandler implements HttpHandler {
@java.lang.Override
public void handle(HttpExchange exchange) throws IOException {
try {
- exchange.setAttribute("attr", "val");
- assertEquals("val", exchange.getAttribute("attr"));
+ if ("context".equals(System.getProperty("jdk.httpserver.attributes"))) {
+ exchange.setAttribute("attr", "val");
+ assertEquals("val", exchange.getAttribute("attr"));
+ assertEquals("val", exchange.getHttpContext().getAttributes().get("attr"));
+ } else {
+ assertNull(exchange.getAttribute("attr"));
+ assertEquals("context-val", exchange.getHttpContext().getAttributes().get("attr"));
+ exchange.setAttribute("attr", "val");
+ assertEquals("val", exchange.getAttribute("attr"));
+ assertEquals("context-val", exchange.getHttpContext().getAttributes().get("attr"));
+ }
exchange.setAttribute("attr", null);
assertNull(exchange.getAttribute("attr"));
exchange.sendResponseHeaders(200, -1);