diff --git a/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php
index 4a17d6b..3524ed8 100644
--- a/core/lib/Drupal/Core/Form/FormState.php
+++ b/core/lib/Drupal/Core/Form/FormState.php
@@ -141,16 +141,16 @@ class FormState implements FormStateInterface {
   /**
    * The HTTP form method to use for finding the input for this form.
    *
-   * May be 'post' or 'get'. Defaults to 'post'. Note that 'get' method forms do
+   * May be 'POST' or 'GET'. Defaults to 'POST'. Note that 'GET' method forms do
    * not use form ids so are always considered to be submitted, which can have
-   * unexpected effects. The 'get' method should only be used on forms that do
-   * not change data, as that is exclusively the domain of 'post.'
+   * unexpected effects. The 'GET' method should only be used on forms that do
+   * not change data, as that is exclusively the domain of 'POST.'
    *
    * This property is uncacheable.
    *
    * @var string
    */
-  protected $method = 'post';
+  protected $method = 'POST';
 
   /**
    * If set to TRUE the original, unprocessed form structure will be cached,
@@ -475,6 +475,9 @@ public function getButtons() {
    * {@inheritdoc}
    */
   public function setCached($cache = TRUE) {
+    if ($this->isMethodType('GET')) {
+      throw new \LogicException('NOPE');
+    }
     $this->cache = (bool) $cache;
     return $this;
   }
@@ -483,7 +486,7 @@ public function setCached($cache = TRUE) {
    * {@inheritdoc}
    */
   public function isCached() {
-    return empty($this->no_cache) && $this->cache;
+    return empty($this->no_cache) && $this->cache && $this->isMethodType('POST');
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
index a3b362c..ca4806a 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -128,14 +128,7 @@ public static function preRenderGroup($element) {
    * @see self::preRenderAjaxForm()
    */
   public static function processAjaxForm(&$element, FormStateInterface $form_state, &$complete_form) {
-    $element = static::preRenderAjaxForm($element);
-
-    // If the element was processed as an #ajax element, and a custom URL was
-    // provided, set the form to be cached.
-    if (!empty($element['#ajax_processed']) && !empty($element['#ajax']['url'])) {
-      $form_state->setCached();
-    }
-    return $element;
+    return static::preRenderAjaxForm($element);
   }
 
   /**
@@ -149,7 +142,6 @@ public static function processAjaxForm(&$element, FormStateInterface $form_state
    *   Properties used:
    *   - #ajax['event']
    *   - #ajax['prevent']
-   *   - #ajax['url']
    *   - #ajax['callback']
    *   - #ajax['options']
    *   - #ajax['wrapper']
@@ -169,7 +161,7 @@ public static function preRenderAjaxForm($element) {
     $element['#ajax_processed'] = FALSE;
 
     // Nothing to do if there are no Ajax settings.
-    if (empty($element['#ajax'])) {
+    if (!isset($element['#ajax'])) {
       return $element;
     }
 
@@ -230,31 +222,30 @@ public static function preRenderAjaxForm($element) {
       $element['#attached']['library'][] = 'core/drupal.ajax';
 
       $settings = $element['#ajax'];
+      // Do not support custom URLs, only allow this method to use it for
+      // internal reasons.
+      if (isset($settings['url'])) {
+        throw new \InvalidArgumentException('Custom ajax URLs are no longer supported');
+      }
+      $settings['url'] = NULL;
 
-      // Assign default settings. When 'url' is set to NULL, ajax.js submits the
-      // Ajax request to the same URL as the form or link destination is for
-      // someone with JavaScript disabled. This is generally preferred as a way to
-      // ensure consistent server processing for js and no-js users, and Drupal's
-      // content negotiation takes care of formatting the response appropriately.
-      // However, 'url' and 'options' may be set when wanting server processing
-      // to be substantially different for a JavaScript triggered submission.
-      // One such substantial difference is form elements that use
-      // #ajax['callback'] for determining which part of the form needs
-      // re-rendering. For that, we have a special 'system.ajax' route which
-      // must be manually set.
+      // Assign default settings.
       $settings += [
-        'url' => NULL,
-        'options' => ['query' => []],
         'dialogType' => 'ajax',
       ];
-      if (array_key_exists('callback', $settings) && !isset($settings['url'])) {
-        $settings['url'] = Url::fromRoute('<current>');
+      if (array_key_exists('callback', $settings)) {
         // Add all the current query parameters in order to ensure that we build
         // the same form on the AJAX POST requests. For example,
         // \Drupal\user\AccountForm takes query parameters into account in order
         // to hide the password field dynamically.
-        $settings['options']['query'] += \Drupal::request()->query->all();
-        $settings['options']['query'][FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;
+        $query = \Drupal::request()->query->all();
+        $query[FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;
+
+        $settings += ['options' => []];
+        $settings['options'] += ['query' => []];
+        $options['query'] = $settings['options']['query'] + $query;
+
+        $settings['url'] = Url::fromRoute('<current>', [], $options)->toString();
       }
 
       // @todo Legacy support. Remove in Drupal 8.
@@ -262,13 +253,6 @@ public static function preRenderAjaxForm($element) {
         $settings['method'] = 'replaceWith';
       }
 
-      // Convert \Drupal\Core\Url object to string.
-      if (isset($settings['url']) && $settings['url'] instanceof Url) {
-        $settings['url'] = $settings['url']->setOptions($settings['options'])->toString();
-      }
-      else {
-        $settings['url'] = NULL;
-      }
       unset($settings['options']);
 
       // Add special data to $settings['submit'] so that when this element
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index a006284..f1399e2 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -1792,6 +1792,7 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
       $element = $this->xpath($xpath);
       $element_id = (string) $element[0]['id'];
       $ajax_settings = $drupal_settings['ajax'][$element_id];
+      debug($ajax_settings);
     }
 
     // Add extra information to the POST data as ajax.js does.
diff --git a/core/modules/system/src/Tests/Ajax/AjaxFormCacheTest.php b/core/modules/system/src/Tests/Ajax/AjaxFormCacheTest.php
index f151c33..cc91f03 100644
--- a/core/modules/system/src/Tests/Ajax/AjaxFormCacheTest.php
+++ b/core/modules/system/src/Tests/Ajax/AjaxFormCacheTest.php
@@ -44,8 +44,9 @@ public function testFormCacheUsage() {
     $this->drupalGet($cached_form_url);
     $this->drupalGet($cached_form_url);
 
-    // The number of cache entries should be exactly 3.
-    $this->assertEqual(3, count($key_value_expirable->getAll()));
+    // The number of cache entries should be exactly 0, we just start to cache
+    // once we have a POST request.
+    $this->assertEqual(0, count($key_value_expirable->getAll()));
   }
 
   /**
diff --git a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCachedForm.php b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCachedForm.php
index 3b22783..61122bf 100644
--- a/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCachedForm.php
+++ b/core/modules/system/tests/modules/ajax_forms_test/src/Form/AjaxFormsTestCachedForm.php
@@ -34,9 +34,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         'option1' => $this->t('Option 1'),
         'option2' => $this->t('Option 2'),
       ],
-      '#ajax' => [
-        'url' => Url::fromRoute('system.ajax'),
-      ],
+      '#ajax' => [],
     ];
     return $form;
   }
diff --git a/core/modules/views/src/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php
index 386456c..a2b64ee 100644
--- a/core/modules/views/src/Plugin/views/field/Field.php
+++ b/core/modules/views/src/Plugin/views/field/Field.php
@@ -448,7 +448,6 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
       '#options' => $formatters,
       '#default_value' => $this->options['type'],
       '#ajax' => array(
-        'url' => views_ui_build_form_url($form_state),
       ),
       '#submit' => array(array($this, 'submitTemporaryForm')),
       '#executes_submit_callback' => TRUE,
diff --git a/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php b/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
index e290789..aeb93a5 100644
--- a/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
+++ b/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
@@ -182,7 +182,6 @@ public function buildForm(array $form, FormStateInterface $form_state, Request $
         '#submit' => array(array($this, 'remove')),
         '#limit_validation_errors' => array(array('override')),
         '#ajax' => array(
-          'url' => Url::fromRoute('<current>'),
         ),
         '#button_type' => 'danger',
       );
diff --git a/core/modules/views_ui/src/ViewPreviewForm.php b/core/modules/views_ui/src/ViewPreviewForm.php
index 7ec8f4d..75dfac8 100644
--- a/core/modules/views_ui/src/ViewPreviewForm.php
+++ b/core/modules/views_ui/src/ViewPreviewForm.php
@@ -89,7 +89,6 @@ protected function actions(array $form, FormStateInterface $form_state) {
         '#submit' => array('::submitPreview'),
         '#id' => 'preview-submit',
         '#ajax' => array(
-          'url' => Url::fromRoute('entity.view.preview_form', ['view' => $view->id(), 'display_id' => $this->displayID]),
           'wrapper' => 'views-preview-wrapper',
           'event' => 'click',
           'progress' => array('type' => 'fullscreen'),
diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php
index 05451be..f196210 100644
--- a/core/modules/views_ui/src/ViewUI.php
+++ b/core/modules/views_ui/src/ViewUI.php
@@ -311,11 +311,6 @@ public function getStandardButtons(&$form, FormStateInterface $form_state, $form
       $names = array(t('Apply'), t('Apply and continue'));
     }
 
-    // Views provides its own custom handling of AJAX form submissions. Usually
-    // this happens at the same path, but custom paths may be specified in
-    // $form_state.
-    $form_url = $form_state->get('url') ?: Url::fromRouteMatch(\Drupal::routeMatch());
-
     // Forms that are purely informational set an ok_button flag, so we know not
     // to create an "Apply" button for them.
     if (!$form_state->get('ok_button')) {
@@ -331,7 +326,6 @@ public function getStandardButtons(&$form, FormStateInterface $form_state, $form
         '#submit' => array(array($this, 'standardSubmit')),
         '#button_type' => 'primary',
         '#ajax' => array(
-          'url' => $form_url,
         ),
       );
       // Form API button click detection requires the button's #value to be the
@@ -359,7 +353,6 @@ public function getStandardButtons(&$form, FormStateInterface $form_state, $form
       '#submit' => array($cancel_submit),
       '#validate' => array(),
       '#ajax' => array(
-        'path' => $form_url,
       ),
       '#limit_validation_errors' => array(),
     );
diff --git a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
index b92c5f7..985d355 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
@@ -422,7 +422,12 @@ public function testIsCached($cache_key, $no_cache_key, $expected) {
       'cache' => $cache_key,
       'no_cache' => $no_cache_key,
     ]);
+
+    $form_state->setMethod('POST');
     $this->assertSame($expected, $form_state->isCached());
+
+    $form_state->setMethod('GET');
+    $this->assertSame(FALSE, $form_state->isCached());
   }
 
   /**
@@ -464,6 +469,28 @@ public function providerTestIsCached() {
   }
 
   /**
+   * @covers ::setCached
+   */
+  public function testSetCachedPost() {
+    $form_state = new FormState();
+    $form_state->setMethod('POST');
+    $form_state->setCached();
+    $this->assertSame(TRUE, $form_state->isCached());
+  }
+
+  /**
+   * @covers ::setCached
+   *
+   * @expectedException \LogicException
+   */
+  public function testSetCachedGet() {
+    $form_state = new FormState();
+    $form_state->setMethod('GET');
+    $form_state->setCached();
+    $this->assertSame(TRUE, $form_state->isCached());
+  }
+
+  /**
    * @covers ::isMethodType
    * @covers ::setMethod
    *
diff --git a/core/tests/Drupal/Tests/Core/Render/Element/RenderElementTest.php b/core/tests/Drupal/Tests/Core/Render/Element/RenderElementTest.php
new file mode 100644
index 0000000..bdbf5ef
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Render/Element/RenderElementTest.php
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Render\Element\RenderElementTest.
+ */
+
+namespace Drupal\Tests\Core\Render\Element;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Form\FormBuilderInterface;
+use Drupal\Core\Render\Element\RenderElement;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Render\Element\RenderElement
+ * @group Render
+ */
+class RenderElementTest extends UnitTestCase {
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The container.
+   *
+   * @var \Drupal\Core\DependencyInjection\ContainerBuilder
+   */
+  protected $container;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->requestStack = new RequestStack();
+    $this->container = new ContainerBuilder();
+    $this->container->set('request_stack', $this->requestStack);
+    \Drupal::setContainer($this->container);
+  }
+
+  /**
+   * @covers ::preRenderAjaxForm
+   */
+  public function testPreRenderAjaxForm() {
+    $request = Request::create('/test');
+    $request->query->set('foo', 'bar');
+    $this->requestStack->push($request);
+
+    $prophecy = $this->prophesize('Drupal\Core\Routing\UrlGeneratorInterface');
+    $url = '/test?foo=bar&ajax_form=1';
+    $prophecy->generateFromRoute('<current>', [], ['query' => ['foo' => 'bar', FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]], FALSE)
+      ->willReturn($url);
+
+    $url_generator = $prophecy->reveal();
+    $this->container->set('url_generator', $url_generator);
+
+    $element = [
+      '#type' => 'select',
+      '#id' => 'test',
+      '#ajax' => [
+        'wrapper' => 'foo',
+        'callback' => 'test-callback',
+      ],
+    ];
+
+    $element = RenderElement::preRenderAjaxForm($element);
+
+    $this->assertTrue($element['#ajax_processed']);
+    $this->assertEquals($url, $element['#attached']['drupalSettings']['ajax']['test']['url']);
+  }
+
+  /**
+   * @covers ::preRenderAjaxForm
+   */
+  public function testPreRenderAjaxFormWithQueryOptions() {
+    $request = Request::create('/test');
+    $request->query->set('foo', 'bar');
+    $this->requestStack->push($request);
+
+    $prophecy = $this->prophesize('Drupal\Core\Routing\UrlGeneratorInterface');
+    $url = '/test?foo=bar&other=query&ajax_form=1';
+    $prophecy->generateFromRoute('<current>', [], ['query' => ['foo' => 'bar', 'other' => 'query', FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]], FALSE)
+      ->willReturn($url);
+
+    $url_generator = $prophecy->reveal();
+    $this->container->set('url_generator', $url_generator);
+
+    $element = [
+      '#type' => 'select',
+      '#id' => 'test',
+      '#ajax' => [
+        'wrapper' => 'foo',
+        'callback' => 'test-callback',
+        'options' => [
+          'query' => [
+            'other' => 'query',
+          ]
+        ]
+      ],
+    ];
+
+    $element = RenderElement::preRenderAjaxForm($element);
+
+    $this->assertTrue($element['#ajax_processed']);
+    $this->assertEquals($url, $element['#attached']['drupalSettings']['ajax']['test']['url']);
+  }
+
+}
