 .../path/src/Plugin/Field/FieldType/PathItem.php   | 17 ++++++-
 .../EntityResource/Node/NodeResourceTestBase.php   | 55 ++++++++++++++++++++++
 .../EntityResource/Term/TermResourceTestBase.php   | 43 +++++++++++++++++
 3 files changed, 114 insertions(+), 1 deletion(-)

diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
index 4106381..31901ad 100644
--- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
+++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
@@ -114,6 +114,17 @@ public function set($property_name, $value, $notify = TRUE) {
   /**
    * {@inheritdoc}
    */
+  public function setValue($values, $notify = TRUE) {
+    // Also ensure that existing values are loaded when setting a value, this
+    // ensures that it is possible to set a new value immediately after loading
+    // an entity.
+    $this->ensureLoaded();
+    return parent::setValue($values, $notify);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function postSave($update) {
     if (!$update) {
       if ($this->alias) {
@@ -162,7 +173,9 @@ public static function mainPropertyName() {
    *   https://www.drupal.org/node/2392845.
    */
   protected function ensureLoaded() {
-    if (!$this->isLoaded) {
+    static $is_loading = FALSE;
+
+    if (!$this->isLoaded && !$is_loading) {
       $entity = $this->getEntity();
       if (!$entity->isNew()) {
         // @todo Support loading languge neutral aliases in
@@ -172,7 +185,9 @@ protected function ensureLoaded() {
           'langcode' => $this->getLangcode(),
         ]);
         if ($alias) {
+          $is_loading = TRUE;
           $this->setValue($alias);
+          $is_loading = FALSE;
         }
         else {
           // If there is no existing alias, default the langcode to the current
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
index b4fc553..bbdd0e5 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php
@@ -7,6 +7,7 @@
 use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
 use Drupal\user\Entity\User;
+use GuzzleHttp\RequestOptions;
 
 abstract class NodeResourceTestBase extends EntityResourceTestBase {
 
@@ -215,4 +216,58 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
     return parent::getExpectedUnauthorizedAccessMessage($method);
   }
 
+  /**
+   * Tests PATCHing a node's path with and without 'create url aliases'.
+   *
+   * For a positive test, see the similar test coverage for Term.
+   *
+   * @see \Drupal\Tests\rest\Functional\EntityResource\Term\TermResourceTestBase::testPatchPath()
+   */
+  public function testPatchPath() {
+    $this->initAuthentication();
+    $this->provisionEntityResource();
+    $this->setUpAuthorization('GET');
+    $this->setUpAuthorization('PATCH');
+
+    $url = $this->getEntityResourceUrl()->setOption('query', ['_format' => static::$format]);
+
+    // GET node's current normalization.
+    $response = $this->request('GET', $url, $this->getAuthenticationRequestOptions('GET'));
+    $normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
+
+    // @todo In https://www.drupal.org/node/2824851, we will be able to stop
+    //       unsetting these fields from the normalization, because
+    //       EntityResource::patch() will ignore any fields that are sent that
+    //       match the current value (and obviously we're sending the current
+    //       value).
+    unset($normalization['revision_timestamp']);
+    unset($normalization['revision_uid']);
+    unset($normalization['created']);
+    unset($normalization['changed']);
+    unset($normalization['promote']);
+    unset($normalization['sticky']);
+
+    // Change node's path alias.
+    $normalization['path'][0]['alias'] .= 's-rule-the-world';
+
+    // Create node PATCH request.
+    $request_options = [];
+    $request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
+    $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
+    $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
+
+    // PATCH request: 403 when creating URL aliases unauthorized.
+    $response = $this->request('PATCH', $url, $request_options);
+    $this->assertResourceErrorResponse(403, "Access denied on updating field 'path'.", $response);
+
+    // Grant permission to create URL aliases.
+    $this->grantPermissionsToTestedRole(['create url aliases']);
+
+    // Repeat PATCH request: 200.
+    $response = $this->request('PATCH', $url, $request_options);
+    $this->assertResourceResponse(200, FALSE, $response);
+    $updated_normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
+    $this->assertSame($normalization['path'], $updated_normalization['path']);
+  }
+
 }
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
index 44a4e83..68e6a2e 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
@@ -6,6 +6,7 @@
 use Drupal\taxonomy\Entity\Vocabulary;
 use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait;
 use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
+use GuzzleHttp\RequestOptions;
 
 abstract class TermResourceTestBase extends EntityResourceTestBase {
 
@@ -178,4 +179,46 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
     }
   }
 
+  /**
+   * Tests PATCHing a term's path.
+   *
+   * For a negative test, see the similar test coverage for Node.
+   *
+   * @see \Drupal\Tests\rest\Functional\EntityResource\Node\NodeResourceTestBase::testPatchPath()
+   */
+  public function testPatchPath() {
+    $this->initAuthentication();
+    $this->provisionEntityResource();
+    $this->setUpAuthorization('GET');
+    $this->setUpAuthorization('PATCH');
+
+    $url = $this->getEntityResourceUrl()->setOption('query', ['_format' => static::$format]);
+
+    // GET term's current normalization.
+    $response = $this->request('GET', $url, $this->getAuthenticationRequestOptions('GET'));
+    $normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
+
+    // @todo In https://www.drupal.org/node/2824851, we will be able to stop
+    //       unsetting these fields from the normalization, because
+    //       EntityResource::patch() will ignore any fields that are sent that
+    //       match the current value (and obviously we're sending the current
+    //       value).
+    unset($normalization['changed']);
+
+    // Change term's path alias.
+    $normalization['path'][0]['alias'] .= 's-rule-the-world';
+
+    // Create term PATCH request.
+    $request_options = [];
+    $request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
+    $request_options = array_merge_recursive($request_options, $this->getAuthenticationRequestOptions('PATCH'));
+    $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
+
+    // PATCH request: 200.
+    $response = $this->request('PATCH', $url, $request_options);
+    $this->assertResourceResponse(200, FALSE, $response);
+    $updated_normalization = $this->serializer->decode((string) $response->getBody(), static::$format);
+    $this->assertSame($normalization['path'], $updated_normalization['path']);
+  }
+
 }
