How can I create a GET form with the Form API?
I have looked at Views's exposed filters to copy the behaviour I want.
Views however manipulates the behaviour in several places. I created my own FormBuilder instead (which wraps around the core's FormBuilder) in order to get the behaviour I want.

&lt;?php

namespace Drupal\my_module\Service;

use Drupal\Core\Form\FormBuilder as CoreFormBuilder;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormState;
use Drupal\my_module\Form\AbstractForm;

class FormBuilder {

  /**
   * @var CoreFormBuilder
   */
  private $formBuilder;

  public function __construct(CoreFormBuilder $formBuilder)
  {
    $this-&gt;formBuilder = $formBuilder;
  }

  /**
   * @param $form AbstractForm
   * @param $method string GET|POST
   */
  public function getForm(AbstractForm $form) {
    if ($form-&gt;getMethod() !== 'GET' &amp;&amp; $form-&gt;getMethod() !== 'POST') {
      throw new \Exception('Method must be either GET or POST');
    }

    /**
     * Other than core's FormBuilder, we provide a way to set the form's method to GET
     * Note that the getMethod()-method is not a default FormInterface method.
     * It is part of our own AbstractForm which implements both Drupal\Core\Form\FormInterface,
     * plus our own needed interfaces.
     */
    $form_state = new FormState();
    $form_state-&gt;setMethod($form-&gt;getMethod());

    /**
     * Because we remove the form ID's from the GET request, 
     * we must set the form to always process.
     * And as a result of that, we must also disable the redirect,
     * or the submit process will become an endless loop.
     */
    if ($form-&gt;getMethod() === 'GET') {
      $form_state
        -&gt;setAlwaysProcess()
        -&gt;disableRedirect();
    }

    // Default behaviour copied from core's FormBuilder
    $args = func_get_args();
    // Remove $form_arg from the arguments.
    unset($args[0], $args[1]);
    $form_state-&gt;addBuildInfo('args', array_values($args));
    $build_form = $this-&gt;formBuilder-&gt;buildForm($form, $form_state);

    // Make sure that the form identifiers do not end up in a GET request
    if ($form-&gt;getMethod() === 'GET') {
      $build_form['form_build_id']['#access'] = FALSE;
      $build_form['form_token']['#access'] = FALSE;
      $build_form['form_id']['#access'] = FALSE;
    }

    return $build_form;
  }

}


By using this FormBuilder, it will:


remove form_id, form_build_id and form_token from the rendered GET-form, so that these won't be send to the GET request when the form is being submitted;
and because those identifiers are not there, the form_state will be set to setAlwaysProcess(), so that the form will be processed.