Home > Articles > Programming > Java

Sessions, Cookies, and URLConnections

Mark Wutka shows you how to create a custom URLConnection class to support cookies in a standalone application.
Mark Wutka shows you how to create a custom URLConnection class to support cookies in a standalone application.

Mark is the author of Special Edition Using Java Server Pages and Servlets (2000, Que).

If you've used the World Wide Web for any length of time, you've probably heard about cookies. A cookie is simply a piece of data that the Web server stores in your browser. A browser and a Web server spend most of their time disconnected from each other. The only time they are aware of each other's presence is when the browser retrieves pages, images, or other content from the server. To provide customized content, the Web server needs a way to identify the browser. The cookie solves this problem. The Web browser sends a cookie to the browser. When the browser accesses the server again, it sends the cookie back to the server. That way, the server can distinguish one user from another.

For example, when you log in to a custom news site, the Web server stores a cookie on your browser identifying you. Whenever the browser accesses the Web server, it sends the cookie to the Web server so that the server can give you the news you want.

Java Server Pages and servlets let you store cookies in the browser and retrieve them. Even if you don't use cookies explicitly, you may be using them unknowingly. The HttpSession class, which saves data for a particular client session, uses cookies to identify browser sessions.

The session mechanism works well when you use a browser (assuming that the browser supports cookies), but it doesn't work so well from outside a browser. The problem is that Java's URLConnection class doesn't keep track of cookies. When you access a servlet or a JSP from a standalone Java program, sessions don't work. The Web server tries to send you a cookie, but because the URLConnection class ignores the cookie, you don't send the cookie back to the server the next time you make a request.

Because Java's URLConnection mechanism is extensible, you can create your own custom HttpURLConnection class that supports cookies. You can then use the URL and URLConnection classes exactly as you normally do, and suddenly your JSP and servlet sessions will work.

When the Web server sends a cookie to a browser, it sends a request header called Set-cookie. The format of the header is basically as follows:

Set-cookie: name=value; path=somepath; expires=expiretime

The main part of the cookie is name=value because it specifies the cookie's value. The path specifies the base path for returning the cookie to the Web server. When the browser accesses a page on the server, it compares the page's path to the paths for all cookies from the server. If the cookie's path is /, the browser sends the cookie in every request to the server.

The expires setting tells the browser how long to hold on to the cookie. If there is no expiry time, the cookie disappears when you close down the browser.

The following Java class represents a cookie value and has the capability to parse its data from a Set-cookie header value:

package com.wutka.net.http;

import java.net.*;
import java.util.*;

// This class represents a Netscape cookie. It can parse its
// values from the string from a Set-cookie: response (without
// the Set-cookie: portion, of course). It is little more than
// a fancy data structure.

public class Cookie
// Define the standard cookie fields

    public String name;
    public String value;
    public Date expires;
    public String domain;
    public String path;
    public boolean isSecure;

// cookieString is the original string from the Set-cookie header.
//  You just save it rather than trying to regenerate for the toString
// method. Note that because this class can initialize itself from this
// string, it can be used to save a persistent copy of this class!

    public String cookieString;

// Initialize the cookie based on the origin URL and the cookie string

    public Cookie(URL sourceURL, String cookieValue)
        domain = sourceURL.getHost();
        path = sourceURL.getFile();


// Initialize the cookie based solely on its cookie string
    public Cookie(String cookieValue)

// Parse a cookie string and initialize the values

    protected void parseCookieValue(String cookieValue)
        cookieString = cookieValue;

// Separate out the various fields which are separated by ;'s

        StringTokenizer tokenizer = new StringTokenizer(
            cookieValue, ";");

        while (tokenizer.hasMoreTokens()) {

// Eliminate leading and trailing whitespace
            String token = tokenizer.nextToken().trim();

// See if the field is of the form name=value or if it is just
// a name by itself.
            int eqIndex = token.indexOf('=');

            String key, value;

// If it is just a name by itself, set the field's value to null
            if (eqIndex == -1) {
                key = token;
                value = null;

// Otherwise, the name is to the left of the '=', value is to the right
            } else {
                key = token.substring(0, eqIndex);
                value = token.substring(eqIndex+1);

            isSecure = false;

// Convert the key to lowercase for comparison with the standard field names

            String lcKey = key.toLowerCase();

            if (lcKey.equals("expires")) {
                expires = new Date(value);
            } else if (lcKey.equals("domain")) {
                if (isValidDomain(value)) {
                    domain = value;
            } else if (lcKey.equals("path")) {
                path = value;
            } else if (lcKey.equals("secure")) {
                isSecure = true;

// If the key wasn't a standard field name, it must be the cookie's name, so
// you don't use the lowercase version of the name here.
            } else {
                name = key;
                this.value = value;

// isValidDomain performs the standard cookie domain check. A cookie
// domain must have at least two portions if it ends in
// .com, .edu, .net, .org, .gov, .mil, or .int. If it ends in something
// else, it must have 3 portions. In other words, you can't specify
// .com as a domain because it has to be something.com, and you can't specify
// .ga.us as a domain because it has to be something.ga.us.

    protected boolean isValidDomain(String domain)

// Eliminate the leading period for this check
        if (domain.charAt(0) == '.') domain = domain.substring(1);

        StringTokenizer tokenizer = new StringTokenizer(domain, ".");
        int nameCount = 0;

// Just count the number of names and save the last one you saw
String lastName = "";
        while (tokenizer.hasMoreTokens()) {
            lastName = tokenizer.nextToken();

// At this point, nameCount is the number of sections of the domain
// and lastName is the last section.

// More than 2 sections is okay for everyone
        if (nameCount > 2) return true;

// Less than 2 is bad for everyone
        if (nameCount < 2) return false;

// Exactly two, you better match one of these 7 domain types

        if (lastName.equals("com") || lastName.equals("edu") ||
            lastName.equals("net") || lastName.equals("org") ||
            lastName.equals("gov") || lastName.equals("mil") ||
            lastName.equals("int")) return true;

// Nope, you fail - bad domain!
        return false;

// You use the cookie string as originally set in the Set-cookie header
// field as the string value of this cookie. It is unique, and if you write
// this string to a file, you can completely regenerate this object from
// this string, so you can read the cookie back out of a file.

    public String toString()
        return cookieString;

Parsing the cookie value is only half the battle. You must also figure out which cookies to send for a particular URL. To send a cookie to a Web server, you set a request property called Cookie containing the name=value pairs for every cookie you want to send, separated by semicolons.

The following Java class keeps track of cookie values and, given a URL, generates a cookie string that you can send back to a Web server.

package com.wutka.net.http;

import java.net.*;
import java.io.*;
import java.util.*;

// This class is used to keep track of all known cookies. It
// is your responsibility to load it when your application starts
// and to save it before you quit. You must also manually insert the
// cookies in the database and check for them when doing a GET.

public class CookieDatabase extends Object
    protected static Vector cookies;

// Initialize the cookie table from a file

    public static void loadCookies(String cookieFile)
    throws IOException

// If the cookie table hasn't been created, create it

        if (cookies == null) {
            cookies = new Vector();

// Open the file
        DataInputStream inStream = new DataInputStream(
            new FileInputStream(cookieFile));

        String line;

// Read lines from the file and create cookies from the line.
// The lines should have been written using the toString method
// in the Cookie class - that way you can just pass the lines
// to the Cookie constructor.

        while ((line = inStream.readLine()) != null) {
            Cookie cookie = new Cookie(line);

// Add the cookie to the cookie table

// Save the cookie table to a file

    public static void saveCookies(String cookieFile)
    throws IOException
// If the table isn't here, create it - so you'll create an empty file,
// no big deal, really.

        if (cookies == null) {
            cookies = new Vector();

        PrintStream outStream = new PrintStream(
            new FileOutputStream(cookieFile));

        Enumeration e = cookies.elements();

// Write out every cookie in the table using the cookie's toString method.

        while (e.hasMoreElements()) {
            Cookie cookie = (Cookie) e.nextElement();


// add a new cookie to the table. If the cookie's name collides with an
// existing cookie, replace the old one.

    public static void addCookie(Cookie cookie)
        if (cookies == null) {
            cookies = new Vector();

// Go through the cookie table and see if there are any cookies with
// the same domain name, same name, and same path.

        for (int i=0; i < cookies.size(); i++) {
            Cookie currCookie = (Cookie) cookies.elementAt(i);
            if (!currCookie.domain.equals(
                cookie.domain)) continue;
            if (currCookie.name.equals(cookie.name) &&
                currCookie.path.equals(cookie.path)) {

// Looks like you found a match, so replace the old one with this one
                cookies.setElementAt(cookie, i);

// No duplicates, so it's okay to add this one to the end of the table


// getCookieString does some rather ugly things. First, it finds all the
// cookies that are supposed to be sent for a particular URL. Then
// it sorts them by path length, sending the longest path first (that's
// what Netscape's specs say to do - I'm only following orders).

    public static String getCookieString(URL destURL)
        if (cookies == null) {
            cookies = new Vector();

// sendCookies will hold all the cookies you need to send
        Vector sendCookies = new Vector();

// currDate will be used to prune out expired cookies as you go along

        Date currDate = new Date();

        for (int i=0; i < cookies.size();) {
            Cookie cookie = (Cookie) cookies.elementAt(i);

// See if the current cookie has expired. If so, remove it

            if ((cookie.expires != null) && (currDate.after(
                cookie.expires))) {

// You only increment i if you haven't removed the current element

// If this cookie's domain doesn't match the URL's host, go to the next one
            if (!destURL.getHost().endsWith(cookie.domain)) {

// If the paths don't match, go to the next one
            if (!destURL.getFile().startsWith(cookie.path)) {

// Okay, you've determined that the current cookie matches the URL, now
// add it to the sendCookies vector in the proper place (that is, ensure
// that the vector goes from longest to shortest path).

            int j;
            for (j=0; j < sendCookies.size(); j++) {
                Cookie currCookie = (Cookie) sendCookies.

// If this cookie's path is longer than the cookie[j], you should insert
// it at position j.
                if (cookie.path.length() <
                    currCookie.path.length()) {

// If j is less than the array size, j represents the insertion point
            if (j < sendCookies.size()) {
                sendCookies.insertElementAt(cookie, j);

// Otherwise, add the cookie to the end
            } else {

// Now that the sendCookies array is nicely initialized and sorted, create
// a string of name=value pairs for all the valid cookies

        String cookieString = "";

        Enumeration e = sendCookies.elements();
        boolean firstCookie = true;

        while (e.hasMoreElements()) {
            Cookie cookie = (Cookie) e.nextElement();

            if (!firstCookie) cookieString += "; ";
            cookieString += cookie.name + "=" + cookie.value;
            firstCookie = false;

// Return null if there are no valid cookies
        if (cookieString.length() == 0) return null;
        return cookieString;

Now that you have a way to parse cookies and store them, you need a way to look for cookies automatically whenever you use the URLConnection class. The URL and URLConnection classes enable you to create your own implementation of a particular protocol. In this case, however, you want to use the existing implementation of the HTTP protocol—it's far too painful to implement the protocol yourself.

When the URL class sees a request for a particular protocol, it looks for a handler for the protocol. It first looks at the java.protocol.handler.pkgs system property. The property may contain a list of packages, separated by vertical bars. The class looks for Java classes of the form package.protocol.Handler. For example, if you specify a package of com.wutka.net and you're trying to use the http protocol (your URL starts with http), the URL class looks for a class named com.wutka.net.http.Handler.

If the URL class can't find a handler in any of the specified packages, it looks in the package sun.net.www.protocol. For example, the default handler for http is sun.net.www.protocol.http.Handler. Some Java implementations, including Microsoft's, may use a different default handler.

The following Java class creates an instance of the default http protocol handler and then creates a special wrapper class that adds cookie functionality to the connection:

package com.wutka.net.http;

import java.net.*;
import java.io.*;

/** Handler for HTTP URL connections. Instantiates the real handler
 *  and the cookie-handling wrapper class.
public class Handler extends URLStreamHandler
    public Handler()

    public URLConnection openConnection(URL url)
        throws IOException
// See if the user has specified any proxy settings
        String proxyHost = System.getProperty("http.proxyHost");
        int proxyPort = -1;
            proxyPort = Integer.parseInt(
                System.getProperty("http.proxyPort", "-1"));
        catch (Exception exc)
            throw new IOException("Error parsing http.proxyPort: "+

// Create the real connection using the built-in HTTP connection class
        URLConnection conn =
            new sun.net.www.protocol.http.HttpURLConnection(url,
                proxyHost, proxyPort);

// Create the wrapper class for retrieving cookies from the response
        return new HttpURLConnectionWrapper(url,
            (HttpURLConnection) conn);

The HttpURLConnection class contains quite a few methods—you certainly wouldn't want to implement them all yourself. To add cookie functionality, you really need to do only two things. First, when you create the connection, examine the cookie database to see if there are any cookies you need to send. If you need to send cookies, create a Cookie header value with the list of cookies.

Second, when you read the response from the server, look for Set-cookie header values in the response. Every method that reads response-oriented data calls getInputStream first to make sure that the connection object has read the response. You simply need to override the getInputStream method so that it checks for cookies before returning the input stream.

The following class adds cookie functionality to an existing HttpURLConnection. Almost all the methods are simple passthroughs that call corresponding methods in the underlying connection.

package com.wutka.net.http;

import java.net.*;
import java.io.*;
import java.security.*;

public class HttpURLConnectionWrapper extends HttpURLConnection
    protected HttpURLConnection impl;
    protected InputStream inputStream;

    public HttpURLConnectionWrapper(URL url, HttpURLConnection handler)

        impl = handler;
        inputStream = null;

// See if there are any cookies for this URL
        String cookieString = CookieDatabase.getCookieString(url);

        if (cookieString != null)
// If there are any cookies, store them in the request
            impl.setRequestProperty("Cookie", cookieString);

/** Looks for cookies in the response before returning the input stream */

    public InputStream getInputStream()
        throws IOException
// Only look for cookies the first time
        if (inputStream == null)
            inputStream = impl.getInputStream();


        return inputStream;

    public void processCookies()
        int i = 1;

        String headerFieldKey = getHeaderFieldKey(i);

// Loop through the various header fields. There can be multiple
// set-cookie header fields, but the URLConnection class can access only
// a single one by name. You must loop through the headers by index number
// instead of by name.

        while (headerFieldKey != null)
// If the header field is a cookie, parse it and add it to the database
            if (headerFieldKey.equalsIgnoreCase("set-cookie"))
                String headerField = getHeaderField(i);

                Cookie cookie = new Cookie(url, headerField);

            headerFieldKey = getHeaderFieldKey(i);

// The rest of the routines simply call methods in the real implementation

    public void connect() throws IOException { impl.connect(); }
    public boolean getAllowUserInteraction()
        { return impl.getAllowUserInteraction(); }
    public Object getContent() throws IOException
        { return impl.getContent(); }

// Include this for JDK 1.3 support
//    public Object getContent(Class[] classes) throws IOException
//        { return impl.getContent(classes); }

    public String getContentEncoding() { return impl.getContentEncoding(); }
    public int getContentLength() { return impl.getContentLength(); }
    public String getContentType() { return impl.getContentType(); }
    public long getDate() { return impl.getDate(); }
    public boolean getDefaultUseCaches() { return impl.getDefaultUseCaches(); }
    public boolean getDoInput() { return impl.getDoInput(); }
    public boolean getDoOutput() { return impl.getDoOutput(); }
    public long getExpiration() { return impl.getExpiration(); }
    public String getHeaderField(int n) { return impl.getHeaderField(n); }
    public String getHeaderField(String n) { return impl.getHeaderField(n); }
    public long getHeaderFieldDate(String n, long defaultValue)
        { return impl.getHeaderFieldDate(n, defaultValue); }
    public int getHeaderFieldInt(String n, int defaultValue)
        { return impl.getHeaderFieldInt(n, defaultValue); }
    public String getHeaderFieldKey(int n)
        { return impl.getHeaderFieldKey(n); }
    public long getIfModifiedSince() { return impl.getIfModifiedSince(); }
    public long getLastModified() { return impl.getLastModified(); }
    public OutputStream getOutputStream() throws IOException
        { return impl.getOutputStream(); }
    public Permission getPermission() throws IOException
        { return impl.getPermission(); }
    public String getRequestProperty(String key)
        { return impl.getRequestProperty(key); }
    public URL getURL() { return impl.getURL(); }
    public boolean getUseCaches() { return impl.getUseCaches(); }
    public void setAllowUserInteraction(boolean allowUserInteraction)
        { impl.setAllowUserInteraction(allowUserInteraction); }
    public void setDefaultUseCaches(boolean defaultUseCaches)
        { impl.setDefaultUseCaches(defaultUseCaches); }
    public void setDoInput(boolean doInput) { impl.setDoInput(doInput); }
    public void setDoOutput(boolean doOutput) { impl.setDoOutput(doOutput); }
    public void setIfModifiedSince(long ifModifiedSince)
        { impl.setIfModifiedSince(ifModifiedSince); }
    public void setRequestProperty(String key, String value)
        { impl.setRequestProperty(key, value); }
    public void setUseCaches(boolean useCaches)
        { impl.setUseCaches(useCaches); }
    public String toString() { return impl.toString(); }

    public void disconnect() { impl.disconnect(); }
    public InputStream getErrorStream() { return impl.getErrorStream(); }
    public String getRequestMethod() { return impl.getRequestMethod(); }
    public int getResponseCode() throws IOException
        { return impl.getResponseCode(); }
    public String getResponseMessage() throws IOException
        { return impl.getResponseMessage(); }
    public void setRequestMethod(String method) throws ProtocolException
        { impl.setRequestMethod(method); }
    public boolean usingProxy() { return impl.usingProxy(); }

When you want to use these classes, just make sure to add the following setting to your system properties: java.protocol.handler.pkgs=com.wutka.net. You should now be able to use sessions when you work with Java Server Pages and servlets. If you are using a Java applet, you can't set the system property. You must manually connect the connection object to the HttpURLConnectionWrapper class.

About the Author

Mark Wutka is the president of Wutka Consulting and specializes in helping companies get the most out of Java. He has built numerous Java, JSP, and servlet applications, including several online ordering applications. In a past life, he was the chief architect on a large, object-oriented distributed system providing automation for the flight operations division of a major airline; for nine years he designed and implemented numerous systems in Java, C, C++, and Smalltalk for that same airline. Mark previously contributed chapters to Special Edition Using Java 2 Platform and is the author of Special Edition Using Java Server Pages and Servlets and Hacking Java. His next book, Special Edition Using Java 2 Enterprise Edition, will be available in April.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information

To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.


Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.


If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information

Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.


This site is not directed to children under the age of 13.


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information

If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information

Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents

California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure

Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact

Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice

We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020