Cookie Builder (#895)

A proposal to use builder pattern much more developer friendly than telescoping constructors in `Cookie` and `NewCookie`.

For example to set a `NewCookie` instance with a name and a `SameSite.LAX` attribute no longer need to do this:

`new NewCookie("name", null, null, null, Cookie.DEFAULT_VERSION, null, DEFAULT_MAX_AGE, null, false, false, SameSite.LAX);`

just do that:

`new NewCookie.Builder("name").sameSite(SameSite.LAX).build();`
diff --git a/etc/config/checkstyle.xml b/etc/config/checkstyle.xml
index 58bf000..254bc1b 100644
--- a/etc/config/checkstyle.xml
+++ b/etc/config/checkstyle.xml
@@ -195,10 +195,11 @@
         <!--module name="DoubleCheckedLocking"/-->
         <module name="EmptyStatement"/>
         <module name="EqualsHashCode"/>
-        <module name="HiddenField">
-            <property name="ignoreConstructorParameter" value="true"/>
-            <property name="ignoreSetter" value="true"/>
-        </module>
+        <!-- Following "HiddenField" module is not compatible with builder inner class style. -->
+        <!-- <module name="HiddenField"> -->
+            <!-- <property name="ignoreConstructorParameter" value="true"/> -->
+            <!-- <property name="ignoreSetter" value="true"/> -->
+        <!-- </module> -->
         <module name="IllegalInstantiation"/>
         <module name="InnerAssignment"/>
         <!--
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Cookie.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Cookie.java
index 902a012..452235c 100644
--- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/Cookie.java
+++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/Cookie.java
@@ -54,7 +54,9 @@
      * @param domain the host domain for which the cookie is valid.
      * @param version the version of the specification to which the cookie complies.
      * @throws IllegalArgumentException if name is {@code null}.
+     * @deprecated This constructor will be removed in a future version. Please use {@link Cookie.Builder} instead.
      */
+    @Deprecated
     public Cookie(final String name, final String value, final String path, final String domain, final int version)
             throws IllegalArgumentException {
         if (name == null) {
@@ -75,7 +77,9 @@
      * @param path the URI path for which the cookie is valid.
      * @param domain the host domain for which the cookie is valid.
      * @throws IllegalArgumentException if name is {@code null}.
+     * @deprecated This constructor will be removed in a future version. Please use {@link Cookie.Builder} instead.
      */
+    @Deprecated
     public Cookie(final String name, final String value, final String path, final String domain)
             throws IllegalArgumentException {
         this(name, value, path, domain, DEFAULT_VERSION);
@@ -87,13 +91,33 @@
      * @param name the name of the cookie.
      * @param value the value of the cookie.
      * @throws IllegalArgumentException if name is {@code null}.
+     * @deprecated This constructor will be removed in a future version. Please use {@link Cookie.Builder} instead.
      */
+    @Deprecated
     public Cookie(final String name, final String value)
             throws IllegalArgumentException {
         this(name, value, null, null);
     }
 
     /**
+     * Create a new instance from the supplied {@link AbstractCookieBuilder} instance.
+     *
+     * @param builder the builder.
+     * @throws IllegalArgumentException if {@code builder.name} is {@code null}.
+     * @since 3.1
+     */
+    protected Cookie(AbstractCookieBuilder<?> builder) throws IllegalArgumentException {
+        if (builder.name == null) {
+            throw new IllegalArgumentException("name==null");
+        }
+        this.name = builder.name;
+        this.value = builder.value;
+        this.version = builder.version;
+        this.domain = builder.domain;
+        this.path = builder.path;
+    }
+
+    /**
      * Creates a new instance of {@code Cookie} by parsing the supplied string.
      *
      * @param value the cookie string.
@@ -216,4 +240,119 @@
         }
         return true;
     }
+
+    /**
+     * JAX-RS {@link Cookie} builder class.
+     * <p>
+     * Cookie builder provides methods that let you conveniently configure and subsequently build a new
+     * {@code Cookie} instance.
+     * </p>
+     * For example:
+     *
+     * <pre>
+     * Cookie cookie = new Cookie.Builder("name")
+     *         .path("/")
+     *         .domain("domain.com")
+     *         .build();
+     * </pre>
+     *
+     * @since 3.1
+     */
+    public static class Builder extends AbstractCookieBuilder<Builder> {
+
+        public Builder(String name) {
+            super(name);
+        }
+
+        @Override
+        public Cookie build() {
+            return new Cookie(this);
+        }
+
+    }
+
+    /**
+     * JAX-RS abstract {@link Cookie} builder class.
+     *
+     * @param <T> the current AbstractCookieBuilder type.
+     *
+     * @since 3.1
+     */
+    public abstract static class AbstractCookieBuilder<T extends AbstractCookieBuilder<T>> {
+
+        private final String name;
+
+        private String value;
+        private int version = DEFAULT_VERSION;
+        private String path;
+        private String domain;
+
+        /**
+         * Create a new instance.
+         *
+         * @param name the name of the cookie.
+         */
+        public AbstractCookieBuilder(String name) {
+            this.name = name;
+        }
+
+        /**
+         * Set the value of the cookie.
+         *
+         * @param value the value of the cookie.
+         * @return the updated builder instance.
+         */
+        public T value(String value) {
+            this.value = value;
+            return self();
+        }
+
+        /**
+         * Set the version of the cookie. Defaults to {@link Cookie#DEFAULT_VERSION}
+         *
+         * @param version the version of the specification to which the cookie complies.
+         * @return the updated builder instance.
+         */
+        public T version(int version) {
+            this.version = version;
+            return self();
+        }
+
+        /**
+         * Set the path of the cookie.
+         *
+         * @param path the URI path for which the cookie is valid.
+         * @return the updated builder instance.
+         */
+        public T path(String path) {
+            this.path = path;
+            return self();
+        }
+
+        /**
+         * Set the domain of the cookie.
+         *
+         * @param domain the host domain for which the cookie is valid.
+         * @return the updated builder instance.
+         */
+        public T domain(String domain) {
+            this.domain = domain;
+            return self();
+        }
+
+        @SuppressWarnings("unchecked")
+        private T self() {
+            return (T) this;
+        }
+
+        /**
+         * Build a new {@link Cookie} instance using all the configuration previously specified in this builder.
+         *
+         * @return a new {@link Cookie} instance.
+         * @throws IllegalArgumentException if name is {@code null}.
+         */
+        public abstract Cookie build();
+
+    }
+
 }
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/NewCookie.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/NewCookie.java
index 4e880fb..bb76d49 100644
--- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/NewCookie.java
+++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/NewCookie.java
@@ -55,7 +55,9 @@
      * @param name the name of the cookie.
      * @param value the value of the cookie.
      * @throws IllegalArgumentException if name is {@code null}.
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final String name, final String value) {
         this(name, value, null, null, DEFAULT_VERSION, null, DEFAULT_MAX_AGE, null, false, false, null);
     }
@@ -71,7 +73,9 @@
      * @param maxAge the maximum age of the cookie in seconds.
      * @param secure specifies whether the cookie will only be sent over a secure connection.
      * @throws IllegalArgumentException if name is {@code null}.
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final String name,
             final String value,
             final String path,
@@ -95,7 +99,9 @@
      * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
      * @throws IllegalArgumentException if name is {@code null}.
      * @since 2.0
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final String name,
             final String value,
             final String path,
@@ -119,7 +125,9 @@
      * @param maxAge the maximum age of the cookie in seconds
      * @param secure specifies whether the cookie will only be sent over a secure connection
      * @throws IllegalArgumentException if name is {@code null}.
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final String name,
             final String value,
             final String path,
@@ -146,7 +154,9 @@
      * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
      * @throws IllegalArgumentException if name is {@code null}.
      * @since 2.0
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final String name,
             final String value,
             final String path,
@@ -176,7 +186,9 @@
      * @param sameSite specifies the value of the {@code SameSite} cookie attribute
      * @throws IllegalArgumentException if name is {@code null}.
      * @since 3.1
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final String name,
             final String value,
             final String path,
@@ -202,7 +214,9 @@
      *
      * @param cookie the cookie to clone.
      * @throws IllegalArgumentException if cookie is {@code null}.
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final Cookie cookie) {
         this(cookie, null, DEFAULT_MAX_AGE, null, false, false, null);
     }
@@ -215,7 +229,9 @@
      * @param maxAge the maximum age of the cookie in seconds.
      * @param secure specifies whether the cookie will only be sent over a secure connection.
      * @throws IllegalArgumentException if cookie is {@code null}.
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final Cookie cookie, final String comment, final int maxAge, final boolean secure) {
         this(cookie, comment, maxAge, null, secure, false, null);
     }
@@ -231,7 +247,9 @@
      * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
      * @throws IllegalArgumentException if cookie is {@code null}.
      * @since 2.0
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final Cookie cookie, final String comment, final int maxAge, final Date expiry, final boolean secure, final boolean httpOnly) {
         this(cookie, comment, maxAge, expiry, secure, httpOnly, null);
     }
@@ -249,7 +267,9 @@
      * @param sameSite specifies the value of the {@code SameSite} cookie attribute
      * @throws IllegalArgumentException if cookie is {@code null}.
      * @since 3.1
+     * @deprecated This constructor will be removed in a future version. Please use {@link NewCookie.Builder} instead.
      */
+    @Deprecated
     public NewCookie(final Cookie cookie, final String comment, final int maxAge, final Date expiry, final boolean secure, final boolean httpOnly,
             final SameSite sameSite) {
         super(cookie == null ? null : cookie.getName(),
@@ -266,6 +286,23 @@
     }
 
     /**
+     * Create a new instance from the supplied {@link AbstractNewCookieBuilder} instance.
+     *
+     * @param builder the builder.
+     * @throws IllegalArgumentException if {@code builder.name} is {@code null}.
+     * @since 3.1
+     */
+    protected NewCookie(AbstractNewCookieBuilder<?> builder) {
+        super(builder);
+        this.comment = builder.comment;
+        this.maxAge = builder.maxAge;
+        this.expiry = builder.expiry;
+        this.secure = builder.secure;
+        this.httpOnly = builder.httpOnly;
+        this.sameSite = builder.sameSite;
+    }
+
+    /**
      * Creates a new instance of NewCookie by parsing the supplied string.
      *
      * @param value the cookie string.
@@ -475,4 +512,182 @@
 
     }
 
+    /**
+     * JAX-RS {@link NewCookie} builder class.
+     * <p>
+     * New Cookie builder provides methods that let you conveniently configure and subsequently build a new
+     * {@code NewCookie} instance.
+     * </p>
+     * For example:
+     *
+     * <pre>
+     * NewCookie cookie = new NewCookie.Builder("name")
+     *         .path("/")
+     *         .domain("domain.com")
+     *         .sameSite(SameSite.LAX)
+     *         .build();
+     * </pre>
+     *
+     * @since 3.1
+     */
+    public static class Builder extends AbstractNewCookieBuilder<Builder> {
+
+        /**
+         * Create a new instance.
+         *
+         * @param name the name of the cookie.
+         */
+        public Builder(String name) {
+            super(name);
+        }
+
+        /**
+         * Create a new instance supplementing the information in the supplied cookie.
+         *
+         * @param cookie the cookie to clone.
+         */
+        public Builder(Cookie cookie) {
+            super(cookie);
+        }
+
+        @Override
+        public NewCookie build() {
+            return new NewCookie(this);
+        }
+
+    }
+
+    /**
+     * JAX-RS abstract {@link NewCookie} builder class.
+     *
+     * @param <T> the current AbstractNewCookieBuilder type.
+     *
+     * @since 3.1
+     */
+    public abstract static class AbstractNewCookieBuilder<T extends AbstractNewCookieBuilder<T>> extends AbstractCookieBuilder<AbstractNewCookieBuilder<T>> {
+
+        private String comment;
+        private int maxAge = DEFAULT_MAX_AGE;
+        private Date expiry;
+        private boolean secure;
+        private boolean httpOnly;
+        private SameSite sameSite;
+
+        /**
+         * Create a new instance.
+         *
+         * @param name the name of the cookie.
+         */
+        public AbstractNewCookieBuilder(String name) {
+            super(name);
+        }
+
+        /**
+         * Create a new instance supplementing the information in the supplied cookie.
+         *
+         * @param cookie the cookie to clone.
+         */
+        public AbstractNewCookieBuilder(Cookie cookie) {
+            super(cookie == null ? null : cookie.getName());
+            if (cookie != null) {
+                value(cookie.getValue());
+                path(cookie.getPath());
+                domain(cookie.getDomain());
+                version(cookie.getVersion());
+            }
+        }
+
+        /**
+         * Set the comment associated with the cookie.
+         *
+         * @param comment the comment.
+         * @return the updated builder instance.
+         */
+        public T comment(String comment) {
+            this.comment = comment;
+            return self();
+        }
+
+        /**
+         * Set the maximum age of the the cookie in seconds. Cookies older than the maximum age are discarded. A cookie can be
+         * unset by sending a new cookie with maximum age of 0 since it will overwrite any existing cookie and then be
+         * immediately discarded. The default value of {@code -1} indicates that the cookie will be discarded at the end of the
+         * browser/application session.
+         *
+         * @param maxAge the maximum age in seconds.
+         * @return the updated builder instance.
+         * @see #expiry(Date)
+         */
+        public T maxAge(int maxAge) {
+            this.maxAge = maxAge;
+            return self();
+        }
+
+        /**
+         * Set the cookie expiry date. Cookies whose expiry date has passed are discarded. A cookie can be unset by setting a
+         * new cookie with an expiry date in the past, typically the lowest possible date that can be set.
+         * <p>
+         * Note that it is recommended to use {@link #maxAge(int) Max-Age} to control cookie expiration, however some browsers
+         * do not understand {@code Max-Age}, in which case setting {@code Expires} parameter may be necessary.
+         * </p>
+         *
+         * @param expiry the cookie expiry date
+         * @return the updated builder instance.
+         * @see #maxAge(int)
+         */
+        public T expiry(Date expiry) {
+            this.expiry = expiry;
+            return self();
+        }
+
+        /**
+         * Whether the cookie will only be sent over a secure connection. Defaults to {@code false}.
+         *
+         * @param secure specifies whether the cookie will only be sent over a secure connection.
+         * @return the updated builder instance.
+         */
+        public T secure(boolean secure) {
+            this.secure = secure;
+            return self();
+        }
+
+        /**
+         * Whether the cookie will only be visible as part of an HTTP request. Defaults to {@code false}.
+         *
+         * @param httpOnly if {@code true} make the cookie HTTP only, i.e. only visible as part of an HTTP request.
+         * @return the updated builder instance.
+         */
+        public T httpOnly(boolean httpOnly) {
+            this.httpOnly = httpOnly;
+            return self();
+        }
+
+        /**
+         * Set the attribute that controls whether the cookie is sent with cross-origin requests, providing protection against
+         * cross-site request forgery.
+         *
+         * @param sameSite specifies the value of the {@code SameSite} cookie attribute.
+         * @return the updated builder instance.
+         */
+        public T sameSite(SameSite sameSite) {
+            this.sameSite = sameSite;
+            return self();
+        }
+
+        @SuppressWarnings("unchecked")
+        private T self() {
+            return (T) this;
+        }
+
+        /**
+         * Build a new {@link NewCookie} instance using all the configuration previously specified in this builder.
+         *
+         * @return a new {@link NewCookie} instance.
+         * @throws IllegalArgumentException if name is {@code null}.
+         */
+        @Override
+        public abstract NewCookie build();
+
+    }
+
 }
diff --git a/jaxrs-api/src/test/java/jakarta/ws/rs/core/CookieTest.java b/jaxrs-api/src/test/java/jakarta/ws/rs/core/CookieTest.java
index afa6ff7..ab5720a 100644
--- a/jaxrs-api/src/test/java/jakarta/ws/rs/core/CookieTest.java
+++ b/jaxrs-api/src/test/java/jakarta/ws/rs/core/CookieTest.java
@@ -20,27 +20,84 @@
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 public class CookieTest extends BaseDelegateTest {
 
-    /**
-     * Test of equals method, of class Cookie and NewCookie.
-     */
     @Test
-    public void testEquals() {
-        Object nullObj = null;
+    public final void shouldReturnFalseWhenComparingCookieToNullObject() {
+        Cookie cookie = new Cookie("name", "value");
+        assertFalse(cookie.equals(null));
+
+        cookie = new Cookie.Builder("name").value("value").build();
+        assertFalse(cookie.equals(null));
+    }
+
+    @Test
+    public final void shouldReturnFalseWhenComparingCookieToNewCookie() {
+        Cookie cookie = new Cookie("name", "value");
+        NewCookie newCookie = new NewCookie("name", "value");
+        assertFalse(cookie.equals(newCookie));
+
+        Cookie thisCookie = new Cookie.Builder("name").value("value").build();
+        NewCookie thatNewCookie = new NewCookie.Builder("name").value("value").build();
+        assertFalse(thisCookie.equals(thatNewCookie));
+    }
+
+    @Test
+    public final void shouldReturnFalseWhenComparingCookiesThatHaveDifferentValues() {
+        Cookie cookie = new Cookie("name", "value");
+        Cookie cookie2 = new Cookie("name", "value2");
+        assertFalse(cookie.equals(cookie2));
+
+        Cookie thisCookie = new Cookie.Builder("name").value("value").build();
+        Cookie thatCookie = new Cookie.Builder("name").value("value2").build();
+        assertFalse(thisCookie.equals(thatCookie));
+    }
+
+    @Test
+    public final void shouldReturnTrueWhenComparingCookiesThatHaveSameValues() {
+
         Cookie cookie = new Cookie("name", "value");
         Cookie cookie1 = new Cookie("name", "value");
-        Cookie cookie2 = new Cookie("name", "value2");
         NewCookie newCookie = new NewCookie("name", "value");
-        NewCookie newCookie1 = new NewCookie("name", "value");
-        NewCookie newCookie2 = new NewCookie("name", "value2");
-        assertFalse(cookie.equals(nullObj));
-        assertFalse(cookie.equals(newCookie));
-        assertFalse(cookie.equals(cookie2));
         assertTrue(cookie.equals(cookie1));
         assertTrue(cookie.equals(newCookie.toCookie()));
-        assertTrue(newCookie.equals(newCookie1));
-        assertFalse(newCookie.equals(newCookie2));
+
+        Cookie thisCookie = new Cookie.Builder("name").value("value").build();
+        Cookie thatCookie = new Cookie.Builder("name").value("value").build();
+        assertTrue(thisCookie.equals(thatCookie));
+
+        thatCookie = new NewCookie.Builder("name").value("value").build().toCookie();
+        assertTrue(thisCookie.equals(thatCookie));
     }
+
+    @Test
+    public final void shouldThrowAnIllegalArgumentExceptionWhenBuildingCookieWithNullName() {
+
+        try {
+            new Cookie(null, "value");
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new Cookie(null, "value", "path", "domain");
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new Cookie(null, "value", "path", "domain", Cookie.DEFAULT_VERSION);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new Cookie.Builder(null).build();
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
 }
diff --git a/jaxrs-api/src/test/java/jakarta/ws/rs/core/NewCookieBuilderTest.java b/jaxrs-api/src/test/java/jakarta/ws/rs/core/NewCookieBuilderTest.java
new file mode 100644
index 0000000..3a629f5
--- /dev/null
+++ b/jaxrs-api/src/test/java/jakarta/ws/rs/core/NewCookieBuilderTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package jakarta.ws.rs.core;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Unit tests for {@link NewCookie.Builder}
+ *
+ * @author Nicolas NESMON
+ * @since 3.1
+ */
+public final class NewCookieBuilderTest extends BaseDelegateTest {
+
+    @Test
+    public final void shouldReturnSuppliedCookieInformationWhenSuppliedCookieIsNotNull() {
+
+        String expectedName = "name";
+        String expectedValue = "value";
+        int expectedVersion = 2;
+        String expectedPath = "/";
+        String expectedDomain = "localhost";
+
+        Cookie cookie = new Cookie.Builder(expectedName)
+                .value(expectedValue)
+                .version(expectedVersion)
+                .path(expectedPath)
+                .domain(expectedDomain)
+                .build();
+        NewCookie newCookie = new NewCookie.Builder(cookie).build();
+
+        assertEquals(expectedName, newCookie.getName());
+        assertEquals(expectedValue, newCookie.getValue());
+        assertEquals(expectedVersion, newCookie.getVersion());
+        assertEquals(expectedPath, newCookie.getPath());
+        assertEquals(expectedDomain, newCookie.getDomain());
+    }
+
+    @Test
+    public final void shouldThrowAnIllegalArgumentExceptionWhenSuppliedCookieIsNull() {
+
+        try {
+            new NewCookie.Builder((Cookie) null).build();
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new NewCookie.Builder((Cookie) null).comment("comment").maxAge(120).secure(true).build();
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
+    public final void shouldReturnNullWhenSameSiteIsNotSet() {
+        NewCookie newCookie = new NewCookie.Builder("name").build();
+        assertNull(newCookie.getSameSite());
+    }
+
+    @Test
+    public final void shouldReturnNullWhenSameSiteIsSetToNull() {
+        NewCookie newCookie = new NewCookie.Builder("name").sameSite(null).build();
+        assertNull(newCookie.getSameSite());
+    }
+
+    @Test
+    public final void shouldReturnSuppliedValueWhenSameSiteIsSetToNonNullValue() {
+
+        NewCookie.Builder newCookieBuilder = new NewCookie.Builder("name");
+
+        NewCookie newCookie = newCookieBuilder.sameSite(NewCookie.SameSite.NONE).build();
+        assertEquals(NewCookie.SameSite.NONE, newCookie.getSameSite());
+
+        newCookie = newCookieBuilder.sameSite(NewCookie.SameSite.LAX).build();
+        assertEquals(NewCookie.SameSite.LAX, newCookie.getSameSite());
+
+        newCookie = newCookieBuilder.sameSite(NewCookie.SameSite.STRICT).build();
+        assertEquals(NewCookie.SameSite.STRICT, newCookie.getSameSite());
+    }
+
+}
diff --git a/jaxrs-api/src/test/java/jakarta/ws/rs/core/NewCookieTest.java b/jaxrs-api/src/test/java/jakarta/ws/rs/core/NewCookieTest.java
index 4b2c88e..7cffb2a 100644
--- a/jaxrs-api/src/test/java/jakarta/ws/rs/core/NewCookieTest.java
+++ b/jaxrs-api/src/test/java/jakarta/ws/rs/core/NewCookieTest.java
@@ -16,54 +16,189 @@
 
 package jakarta.ws.rs.core;
 
+import jakarta.ws.rs.core.NewCookie.SameSite;
+import java.util.Date;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 public class NewCookieTest extends BaseDelegateTest {
 
-    /**
-     * Test of valueOf method, of class NewCookie.
-     */
     @Test
-    public void testCtor() {
-        Cookie c = new Cookie("name", "value");
-        NewCookie nc = new NewCookie(c);
-        assertEquals(nc.getName(), c.getName());
+    public void testSameSite() {
+
+        NewCookie sameSiteOmit = new NewCookie("name", "value", "/", "localhost", 1, null, 0, null, false, false);
+        assertNull(sameSiteOmit.getSameSite());
+        sameSiteOmit = new NewCookie.Builder("name").build();
+        assertNull(sameSiteOmit.getSameSite());
+
+        NewCookie sameSiteNull = new NewCookie("name", "value", "/", "localhost", 1, null, 0, null, false, false, null);
+        assertNull(sameSiteNull.getSameSite());
+        sameSiteNull = new NewCookie.Builder("name").sameSite(null).build();
+        assertNull(sameSiteNull.getSameSite());
+
+        NewCookie sameSiteNone = new NewCookie("name", "value", "/", "localhost", 1, null, 0, null, false, false, NewCookie.SameSite.NONE);
+        assertEquals(NewCookie.SameSite.NONE, sameSiteNone.getSameSite());
+        sameSiteNone = new NewCookie.Builder("name").sameSite(NewCookie.SameSite.NONE).build();
+        assertEquals(NewCookie.SameSite.NONE, sameSiteNone.getSameSite());
+
+        NewCookie sameSiteLax = new NewCookie("name", "value", "/", "localhost", 1, null, 0, null, false, false, NewCookie.SameSite.LAX);
+        assertEquals(NewCookie.SameSite.LAX, sameSiteLax.getSameSite());
+        sameSiteLax = new NewCookie.Builder("name").sameSite(NewCookie.SameSite.LAX).build();
+        assertEquals(NewCookie.SameSite.LAX, sameSiteLax.getSameSite());
+
+        NewCookie sameSiteStrict = new NewCookie("name", "value", "/", "localhost", 1, null, 0, null, false, false, NewCookie.SameSite.STRICT);
+        assertEquals(NewCookie.SameSite.STRICT, sameSiteStrict.getSameSite());
+        sameSiteStrict = new NewCookie.Builder("name").sameSite(NewCookie.SameSite.STRICT).build();
+        assertEquals(NewCookie.SameSite.STRICT, sameSiteStrict.getSameSite());
+
+    }
+
+    @Test
+    public final void shouldReturnFalseWhenComparingNewCookieToNullObject() {
+        NewCookie newCookie = new NewCookie("name", "value");
+        assertFalse(newCookie.equals(null));
+
+        newCookie = new NewCookie.Builder("name").value("value").build();
+        assertFalse(newCookie.equals(null));
+    }
+
+    @Test
+    public final void shouldReturnFalseWhenComparingNewCookieToCookie() {
+        Cookie cookie = new Cookie("name", "value");
+        NewCookie newCookie = new NewCookie("name", "value");
+        assertFalse(newCookie.equals(cookie));
+
+        NewCookie thisNewCookie = new NewCookie.Builder("name").value("value").build();
+        Cookie thatCookie = new Cookie.Builder("name").value("value").build();
+        assertFalse(thisNewCookie.equals(thatCookie));
+    }
+
+    @Test
+    public final void shouldReturnFalseWhenComparingNewCookiesThatHaveDifferentValues() {
+        NewCookie newCookie = new NewCookie("name", "value");
+        NewCookie newCookie2 = new NewCookie("name", "value2");
+        assertFalse(newCookie.equals(newCookie2));
+
+        NewCookie thisNewCookie = new NewCookie.Builder("name").value("value").build();
+        NewCookie thatNewCookie = new NewCookie.Builder("name").value("value2").build();
+        assertFalse(thisNewCookie.equals(thatNewCookie));
+    }
+
+    @Test
+    public final void shouldReturnTrueWhenComparingNewCookiesThatHaveSameValues() {
+        NewCookie newCookie = new NewCookie("name", "value");
+        NewCookie newCookie1 = new NewCookie("name", "value");
+        assertTrue(newCookie.equals(newCookie1));
+
+        NewCookie thisNewCookie = new NewCookie.Builder("name").value("value").build();
+        NewCookie thatNewCookie = new NewCookie.Builder("name").value("value").build();
+        assertTrue(thisNewCookie.equals(thatNewCookie));
+    }
+
+    @Test
+    public final void shouldReturnSuppliedCookiePropertiesWhenBuildingNewCookiesFromCookie() {
+        Cookie cookie = new Cookie("name", "value", "path", "domain", Cookie.DEFAULT_VERSION);
+        NewCookie newCookie = new NewCookie(cookie);
+        assertEquals(newCookie.getName(), cookie.getName());
+        assertEquals(newCookie.getPath(), cookie.getPath());
+        assertEquals(newCookie.getDomain(), cookie.getDomain());
+        assertEquals(newCookie.getVersion(), cookie.getVersion());
+
+        cookie = new Cookie.Builder("name")
+                .value("value")
+                .path("path")
+                .domain("domain")
+                .version(Cookie.DEFAULT_VERSION)
+                .build();
+        newCookie = new NewCookie.Builder(cookie).build();
+        assertEquals(newCookie.getName(), cookie.getName());
+        assertEquals(newCookie.getPath(), cookie.getPath());
+        assertEquals(newCookie.getDomain(), cookie.getDomain());
+        assertEquals(newCookie.getVersion(), cookie.getVersion());
+    }
+
+    @Test
+    public final void shouldThrowAnIllegalArgumentExceptionWhenBuildingNewCookieWithNullName() {
+
         try {
-            nc = new NewCookie(null);
+            new NewCookie(null, null);
             fail("Expected IllegalArgumentException");
         } catch (IllegalArgumentException e) {
         }
+
         try {
-            nc = new NewCookie(null, "comment", 120, true);
+            new NewCookie(null, "value", "path", "domain", "comment", NewCookie.DEFAULT_MAX_AGE, false);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new NewCookie(null, "value", "path", "domain", "comment", NewCookie.DEFAULT_MAX_AGE, false, false);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new NewCookie(null, "value", "path", "domain", Cookie.DEFAULT_VERSION, "comment", NewCookie.DEFAULT_MAX_AGE, false);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new NewCookie(null, "value", "path", "domain", Cookie.DEFAULT_VERSION, "comment", NewCookie.DEFAULT_MAX_AGE, new Date(), false, false);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new NewCookie(null, "value", "path", "domain", Cookie.DEFAULT_VERSION, "comment", NewCookie.DEFAULT_MAX_AGE, new Date(), false, false, SameSite.LAX);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new NewCookie.Builder((String) null).build();
             fail("Expected IllegalArgumentException");
         } catch (IllegalArgumentException e) {
         }
     }
 
     @Test
-    public void testSameSite() {
-        NewCookie sameSiteOmit = new NewCookie("name", "value", "/", "localhost", 1,
-                null, 0, null, false, false);
-        assertNull(sameSiteOmit.getSameSite());
+    public final void shouldThrowAnIllegalArgumentExceptionWhenBuildingNewCookieFromNullCookie() {
 
-        NewCookie sameSiteNull = new NewCookie("name", "value", "/", "localhost", 1,
-                null, 0, null, false, false, null);
-        assertNull(sameSiteNull.getSameSite());
+        try {
+            new NewCookie((Cookie)null);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
 
-        NewCookie sameSiteNone = new NewCookie("name", "value", "/", "localhost", 1,
-                null, 0, null, false, false, NewCookie.SameSite.NONE);
-        assertEquals(NewCookie.SameSite.NONE, sameSiteNone.getSameSite());
+        try {
+            new NewCookie(null, "comment", NewCookie.DEFAULT_MAX_AGE, false);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
 
-        NewCookie sameSiteLax = new NewCookie("name", "value", "/", "localhost", 1,
-                null, 0, null, false, false, NewCookie.SameSite.LAX);
-        assertEquals(NewCookie.SameSite.LAX, sameSiteLax.getSameSite());
+        try {
+            new NewCookie(null, "comment", NewCookie.DEFAULT_MAX_AGE, new Date(), false, false);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
 
-        NewCookie sameSiteStrict = new NewCookie("name", "value", "/", "localhost", 1,
-                null, 0, null, false, false, NewCookie.SameSite.STRICT);
-        assertEquals(NewCookie.SameSite.STRICT, sameSiteStrict.getSameSite());
+        try {
+            new NewCookie(null, "comment", NewCookie.DEFAULT_MAX_AGE, new Date(), false, false, SameSite.LAX);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            new NewCookie.Builder((Cookie) null).build();
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
     }
 }
diff --git a/jaxrs-spec/src/main/asciidoc/chapters/appendix/_changes-since-3.0-release.adoc b/jaxrs-spec/src/main/asciidoc/chapters/appendix/_changes-since-3.0-release.adoc
index e343c6f..c9a9413 100644
--- a/jaxrs-spec/src/main/asciidoc/chapters/appendix/_changes-since-3.0-release.adoc
+++ b/jaxrs-spec/src/main/asciidoc/chapters/appendix/_changes-since-3.0-release.adoc
@@ -30,4 +30,6 @@
 * <<context-injection>>: New section that mentions removal of `@Context`
 injection support in future versions.
 * <<declaring_method_capabilities>>: Clarified resource matching when `Content-Type`
-or `Accept` are missing in request.
\ No newline at end of file
+or `Accept` are missing in request.
+* Constructors of `Cookie` class are deprecated in favor of new `Cookie.Builder` class.
+* Constructors of `NewCookie` are deprecated in favor of new `NewCookie.Builder` class.