diff --git a/jenkins.admin.inc b/jenkins.admin.inc
deleted file mode 100644
index acf8ce2..0000000
--- a/jenkins.admin.inc
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php
-
-/**
- * @file
- * Jenkins administration related functions.
- */
-
-/**
- * Jenkins settings form.
- */
-function jenkins_settings($form, &$form_state) {
-
-  $form['jenkins_base_url'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Jenkins server URL'),
-    '#description' => t('HTTP auth credentials can be included in the URL, like so "http://user:pass@example.com:8080". Note: The value isn\'t encrypted when stored'),
-    '#default_value' => variable_get('jenkins_base_url', 'http://localhost:8080'),
-    '#required' => TRUE,
-  );
-
-  $form['actions'] = array('#type' => 'actions');
-
-  $form['actions']['jenkins_test'] = array(
-    '#type' => 'submit',
-    '#value' => t('Test connection'),
-    '#submit' => array('jenkins_settings_test_submit'),
-  );
-
-  $form['actions']['jenkins_save'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-  );
-
-  return $form;
-}
-
-/**
- * Jenkins settings form validation handler.
- */
-function jenkins_settings_validate($form, &$form_state) {
-  if (!valid_url($form_state['values']['jenkins_base_url'], TRUE)) {
-    form_error($form['jenkins_base_url'], t('"@url" is not a valid URL.', array('@url' => $form_state['values']['jenkins_base_url'])));
-    return FALSE;
-  }
-
-  $response = drupal_http_request($form_state['values']['jenkins_base_url']);
-  if (!empty($response->error)) {
-    $values = array(
-      '@url' => $form_state['values']['jenkins_base_url'],
-      '@error' => $response->error,
-    );
-    form_error($form['jenkins_base_url'], t('Unable to contact jenkins server at "@url". Response: "@error".', $values));
-  }
-}
-
-/**
- * Jenkins settings form submit handler.
- */
-function jenkins_settings_submit($form, &$form_state) {
-  variable_set('jenkins_base_url', $form_state['values']['jenkins_base_url']);
-  // Lifted from system_settings_form_submit().
-  drupal_set_message(t('The configuration options have been saved.'));
-}
-
-/**
- * Jenkins connection test button submit handler.
- */
-function jenkins_settings_test_submit($form, &$form_state) {
-  $current = variable_get('jenkins_base_url');
-  drupal_set_message(t('Drupal was able to connect to @url.', array('@url' => $form_state['values']['jenkins_base_url'])));
-  if ($form_state['values']['jenkins_base_url'] != $current) {
-    drupal_set_message(t('New URL value has not been saved. Please use the "Save" button to save the value permenantly.'), 'warning');
-  }
-}
diff --git a/jenkins.module b/jenkins.module
index 2fdc055..1422448 100644
--- a/jenkins.module
+++ b/jenkins.module
@@ -10,15 +10,6 @@
 function jenkins_menu() {
   $items = array();
 
-  $items['admin/config/services/jenkins'] = array(
-    'title' => 'Jenkins',
-    'description' => 'Configuration for Jenkins API module',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('jenkins_settings'),
-    'access arguments' => array('administer jenkins config'),
-    'file' => 'jenkins.admin.inc',
-  );
-
   $items['jenkins/log/%/%'] = array(
     'title' => 'Jenkins log',
     'description' => 'Stream a jenkins build log',
diff --git a/jenkins.routing.yml b/jenkins.routing.yml
new file mode 100644
index 0000000..72b9176
--- /dev/null
+++ b/jenkins.routing.yml
@@ -0,0 +1,8 @@
+jenkins.service_form:
+  path: /admin/config/services/jenkins
+  defaults:
+    _title: Jenkins
+    _description: Configuration for Jenkins API module
+    _form: \Drupal\jenkins\Form\JenkinsServiceForm
+  requirements:
+    _permission: 'administer jenkins config'
diff --git a/src/Form/JenkinsServiceForm.php b/src/Form/JenkinsServiceForm.php
new file mode 100644
index 0000000..45dccc2
--- /dev/null
+++ b/src/Form/JenkinsServiceForm.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\jenkins\Form\JenkinsServiceForm.
+ */
+
+namespace Drupal\jenkins\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Jenkins Service form.
+ */
+class JenkinsServiceForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'jenkins_service_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['jenkins.settings'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->config('jenkins.settings');
+
+    $form['base_url'] = [
+      '#type' => '#url',
+      '#title' => $this->t('Jenkins server URL'),
+      '#description' => $this->t('HTTP auth credentials can be included in the URL, like so "http://user:pass@example.com:8080". Note: The value isn\'t encrypted when stored'),
+      '#default_value' => $config->get('base_url'),
+      '#required' => TRUE,
+    ];
+    $form['actions'] = ['#type' => 'actions'];
+    $form['actions']['jenkins_test'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Test connection'),
+      '#validate' => ['::validateUrl'],
+      '#submit' => ['::testConnection'],
+    ];
+    $form['actions']['jenkins_save'] = [
+      '#type' => 'submit',
+      '#validate' => ['::validateUrl'],
+      '#value' => $this->t('Save'),
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @todo Replace drupal_set_message() with an injectable service in 8.1.x.
+   *
+   * @see https://www.drupal.org/node/2278383
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    $this->config('jenkins.settings')
+      ->set('base_url', $form_state->getValue('base_url'))
+      ->save();
+
+    drupal_set_message($this->t('The configuration options have been saved.'));
+  }
+
+  /**
+   * Validate if Drupal is able to access the given URL.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @todo Replace drupal_http_request() with Guzzle.
+   *
+   * @see https://www.drupal.org/node/2673940
+   */
+  public function validateUrl(array &$form, FormStateInterface $form_state) {
+    $response = drupal_http_request($form_state['values']['jenkins_base_url']);
+    if (!empty($response->error)) {
+      $values = array(
+        '@url' => $form_state->getValue('base_url'),
+        '@error' => $response->error,
+      );
+      $form_state->setErrorByName('base_url', $this->t('Unable to contact jenkins server at "@url". Response: "@error".', $values));
+    }
+  }
+
+  /**
+   * Jenkins connection test button submit handler.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @todo Replace drupal_set_message() with an injectable service in 8.1.x.
+   *
+   * @see https://www.drupal.org/node/2278383
+   */
+  public function testConnection(array &$form, FormStateInterface $form_state) {
+    $config = $this->config('jenkins.settings');
+    drupal_set_message($this->t('Drupal was able to connect to @url.', array('@url' => $form_state['values']['jenkins_base_url'])));
+    if ($form_state['values']['jenkins_base_url'] != $config->get('base_url')) {
+      drupal_set_message($this->t('New URL value has not been saved. Please use the "Save" button to save the value permanently.'), 'warning');
+    }
+  }
+
+}
