How do I set a cookie? I need to save a cookie for anonymous users in Drupal 8 when users visit the site for the first time, which will be used to display a message.

I can successfully save one using setcookie("FirstTimer", 1, strtotime('+1 year')); but I am sure Drupal 8 should have some alternatives.

I can also use the following code.

$user_is_first_timer = !isset($_COOKIE["Drupal_visitor_FirstTimer"]);


When I open it in Google Chrome, this works, but when I open it in incognito or in Firefox, there is no message. Somehow cached pages are being served.

The code is used in an implementation of hook_page_attachments().
With Drupal 7, Drupal 8, and Drupal 9 you can use user_cookie_save() to store a cookie.
function user_cookie_save(array $values) {
  foreach ($values as $field =&gt; $value) {
    // Set cookie for 365 days.
    setrawcookie('Drupal.visitor.' . $field, rawurlencode($value), REQUEST_TIME + 31536000, '/');
  }
}

The first function parameter is an array. Its keys are the names of the cookies to set; its values are the values to set for those cookies.
user_cookie_save() doesn't allow to set when a cookie expires; cookies created with this function expires after 365 days.
There is just a method in Drupal 8 core class that uses user_cookie_save(): SystemController::compactPage().
public function compactPage($mode) {
  user_cookie_save(['admin_compact_mode' =&gt; $mode == 'on']);
  return $this-&gt;redirect('&lt;front&gt;');
}

Unfortunately, there isn't an equivalent function to read a cookie. Drupal 8 and Drupal 9, to read the cookie saved from SystemController::compactPage() uses system_admin_compact_mode().
function system_admin_compact_mode() {
  // PHP converts dots into underscores in cookie names to avoid problems with
  // its parser, so we use a converted cookie name.
  return \Drupal::request()-&gt;cookies-&gt;get('Drupal_visitor_admin_compact_mode', \Drupal::config('system.site')-&gt;get('admin_compact_mode'));
}

The other function/method I found that stores cookies in Drupal 8 and Drupal 9 is BigPipeController::setNoJsCookie() (implemented since Drupal 8.1.x) which uses the following code.
if ($request-&gt;cookies-&gt;has(BigPipeStrategy::NOJS_COOKIE) || !$request-&gt;hasSession()) {
  throw new AccessDeniedHttpException();
}
if (!$request-&gt;query-&gt;has('destination')) {
  throw new HttpException(400, 'The original location is missing.');
}
$response = new LocalRedirectResponse($request-&gt;query-&gt;get('destination'));

// Set cookie without httpOnly, so that JavaScript can delete it.
$response-&gt;headers-&gt;setCookie(new Cookie(BigPipeStrategy::NOJS_COOKIE, TRUE, 0, '/', NULL, FALSE, FALSE, FALSE, NULL));
$response-&gt;addCacheableDependency((new CacheableMetadata())
  -&gt;addCacheContexts([
    'cookies:' . BigPipeStrategy::NOJS_COOKIE,
    'session.exists',
  ]));
return $response;

Using $response-&gt;headers-&gt;setCookie() gives more control over the cookie parameters (the parameters Cookie::__construct() accepts).