15

When I add a cookie as below:

FacesContext.getCurrentInstance().getExternalContext().addResponseCookie("Test", "Test", null);

Then it works well, but the cookie becomes a session cookie with max age of -1.

When I try as below:

Map<String, Object> properties = new HashMap<>();
properties.put("domain", "test");
properties.put("maxAge", 31536000);
properties.put("secure", false); 
properties.put("path","/");
FacesContext.getCurrentInstance().getExternalContext().addResponseCookie("Test", "Test", properties);

Then I don't see the cookie anywhere. I don't understand why.

I'm using Tomcat 7.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Yaroslav
  • 260
  • 1
  • 3
  • 13

2 Answers2

34

Your specific case failed because the domain was set wrongly. Cookies are domain specific. You can't set a cookie on a different domain. If you don't specify the domain, then it will default to the domain of the current request URI. The Cookie#setDomain() is only useful if you intend to set a cookie on a common or different subdomain. E.g. if you have foo.example.com and bar.example.com, then you could set cookies for the other domain via that method, or set the domain to .example.com (with the leading period!) to share the cookie between both subdomains.

So, this should do in your particular case:

String name = "cookiename";
String value = "cookievalue";
Map<String, Object> properties = new HashMap<>();
properties.put("maxAge", 31536000);
properties.put("path", "/");
externalContext.addResponseCookie(name, URLEncoder.encode(value, "UTF-8"), properties);

Note that explicitly setting the path is very important in case you intend to use the cookie webapp-wide, as it otherwise defaults to the current path which would of course fail when it's set for first time in a subfolder. Such a cookie won't be accessible in any parent folder. The other answer here above doesn't properly take this into account as it unnecessarily and incorrectly reuses an existing cookie instead of creating it brand new. See also a.o. In Java servlet, cookie.getMaxAge() always returns -1.

As to retrieving a cookie in JSF, use ExternalContext#getRequestCookieMap():

Cookie cookie = (Cookie) externalContext.getRequestCookieMap().get(name);
String value = URLDecoder.decode(cookie.getValue(), "UTF-8");
// ...

Note that I'm URL-encoding/decoding the cookie value before setting/retrieving, as you would otherwise run into trouble like asked in the following related questions: Why do cookie values with whitespace arrive at the client side with quotes? and java.lang.IllegalArgumentException: Control character in cookie value or attribute.

That said, I do agree that the JSF API is somewhat opaque as to retrieving and setting cookies. The JSF utility library OmniFaces has several useful utility methods in the Faces utility class for the purpose, which implicitly sets sane defaults as cookie properties and URL-encodes/decodes the value.

// Getting a cookie value.
String value = Faces.getRequestCookie(name);

// Setting a session cookie in current path.
Faces.addResponseCookie(name, value, -1);

// Setting a session cookie in current domain.
Faces.addResponseCookie(name, value, "/", -1);

// Setting a (sub)domain-wide session cookie.
Faces.addResponseCookie(name, value, ".example.com", "/", -1);

// Setting a cookie with max age of 1 year in current domain.
Faces.addResponseCookie(name, value, "/", (int) TimeUnit.DAYS.toSeconds(365));

// Removing a cookie from current domain.
Faces.removeResponseCookie(name, "/");
Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • I use this method `Faces.removeResponseCookie(mykey, "/");` , But the value still remain in cookies .I use `omnifaces-1.14` version. – Zaw Than oo Aug 25 '16 at 11:02
  • Then the domain and/or path is not the same. Inspect it in browser's developer toolset. – BalusC Aug 25 '16 at 11:44
  • @BalusC, In development I have http://localhost:8080/my-app/ and in production www.my-app.com. Is it possible to set cookie in production at "/" path and in development at "my-app/"? I tried externalContext.getRequestContextPath() but it always returns first part after "/". – guest Oct 26 '16 at 17:06
  • What you are looking for is `externalContext.getApplicationContextPath()`. – Tobias Liefke Nov 26 '18 at 15:49
  • Could it be, that Faces.addResponseCookie(name, value, ".example.com", "/", -1); adds the leading period automatically? I used this function with domain.de and a cookie for .domain.de was added. – Tobias Rath Jul 31 '19 at 11:14
28

Try this:

public class CookieHelper {

  public void setCookie(String name, String value, int expiry) {

    FacesContext facesContext = FacesContext.getCurrentInstance();

    HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
    Cookie cookie = null;

    Cookie[] userCookies = request.getCookies();
    if (userCookies != null && userCookies.length > 0 ) {
        for (int i = 0; i < userCookies.length; i++) {
            if (userCookies[i].getName().equals(name)) {
                cookie = userCookies[i];
                break;
            }
        }
    }

    if (cookie != null) {
        cookie.setValue(value);
    } else {
        cookie = new Cookie(name, value);
        cookie.setPath(request.getContextPath());
    }

    cookie.setMaxAge(expiry);

    HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
    response.addCookie(cookie);
  }

  public Cookie getCookie(String name) {

    FacesContext facesContext = FacesContext.getCurrentInstance();

    HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
    Cookie cookie = null;

    Cookie[] userCookies = request.getCookies();
    if (userCookies != null && userCookies.length > 0 ) {
        for (int i = 0; i < userCookies.length; i++) {
            if (userCookies[i].getName().equals(name)) {
                cookie = userCookies[i];
                return cookie;
            }
        }
    }
    return null;
  }
}
Vasil Lukach
  • 3,658
  • 3
  • 31
  • 40