diff --git a/Core/include/Acts/Utilities/FiniteStateMachine.hpp b/Core/include/Acts/Utilities/FiniteStateMachine.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..91c857860fed8dcfd30fdafd9c77042b814a4c40
--- /dev/null
+++ b/Core/include/Acts/Utilities/FiniteStateMachine.hpp
@@ -0,0 +1,234 @@
+// This file is part of the Acts project.
+//
+// Copyright (C) 2019 CERN for the benefit of the Acts project
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#pragma once
+
+#include <optional>
+#include <string_view>
+#include <variant>
+
+#include "Acts/Utilities/TypeTraits.hpp"
+
+namespace Acts {
+
+/// Implementation of a finite state machine engine
+///
+/// Allows setting up a system of states and transitions between them. States
+/// are definedd as empty structs (footprint: 1 byte). Tranitions call functions
+/// using overload resolution. This works by subclassing this class, providing
+/// the deriving type as the first template argument (CRTP) and providing
+/// methods like
+///
+/// ```cpp
+/// event_return on_event(const S&, const E&);
+/// ```
+///
+/// The arguments are the state `S` and the triggered event `E`. Their values
+/// can be discarded (you can attach values to events of course, if you like)
+/// The return type of these functions is effectively `std::optional<State>`, so
+/// you can either return `std::nullopt` to remain in the same state, or an
+/// instance of another state. That state will then become active.
+///
+/// You can also define a method template, which will serve as a catch-all
+/// handler (due to the fact that it will match any state/event combination):
+///
+/// ```cpp
+/// template <typename State, typename Event>
+///   event_return on_event(const State&, const Event&) const {
+///   return Terminated{};
+/// }
+/// ```
+///
+/// If for a given state and event no suitable overload of `on_event` (and you
+/// also haven't defined a catch-all as described above), a transition to
+/// `Terminated` will be triggered. This is essentially equivalent to the method
+/// template above.
+///
+/// If this triggers, it will switch to the `Terminated` state (which is always
+/// included in the FSM).
+///
+/// Additionally, the FSM will attempt to call functions like
+/// ```cpp
+/// void on_enter(const State&);
+/// void on_exit(const State&);
+/// ```
+/// when entering/exiting a state. This can be used to
+/// perform actions regardless of the source or destination state in a
+/// transition to a given state. This is also fired in case a transition to
+/// `Terminated` occurs.
+///
+/// The base class also calls
+/// ```cpp
+/// void on_process(const Event&);
+/// void on_process(const State&, const Event&);
+/// void on_process(const State1& const Event&, const State2&);
+/// ```
+/// during event processing, and allow for things like event and
+/// transition logging.
+///
+/// The `on_event`, `on_enter`, `on_exit` and `on_process` methods need to be
+/// implemented exhaustively, i.e. for all state/event combinations. This might
+/// require you to add catch-all no-op functions like
+/// ```cpp
+/// template <typename...Args>
+/// event_return on_event(Args&&...args) {} // noop
+/// ```
+/// and so on.
+///
+/// The public interface for the user of the FSM are the
+/// ```cpp
+/// template <typename... Args>
+/// void setState(StateVariant state, Args&&... args);
+///
+/// template <typename Event, typename... Args>
+/// void dispatch(Event&& event, Args&&... args) {
+/// ```
+///
+/// `setState` triggers a transition to a given state, `dispatch` triggers
+/// processing on an event from the given state. Both will call the appropriate
+/// `on_exit` and `on_enter` overloads. Both also accept an arbitrary number of
+/// additional arguments that are passed to the `on_event`, `on_exit` and
+/// `on_enter` overloads.
+///
+/// @tparam Derived Class deriving from the FSM
+/// @tparam States Argument pack with the state types that the FSM can be
+///         handled.
+template <typename Derived, typename... States>
+class FiniteStateMachine {
+ public:
+  /// Contractual termination state. Is transitioned to if State+Event do not
+  /// have a transition defined.
+  struct Terminated {
+    /// Name of this state (useful for logging)
+    constexpr static std::string_view name = "Terminated";
+  };
+
+  /// Variant type allowing tagged type erased storage of the current state of
+  /// the FSM.
+  using StateVariant = std::variant<Terminated, States...>;
+
+ protected:
+  using fsm_base = FiniteStateMachine<Derived, States...>;
+
+  using event_return = std::optional<StateVariant>;
+
+ public:
+  /// Default constructor. The default state is taken to be the first in the
+  /// `States` template arguments
+  FiniteStateMachine()
+      : m_state(
+            typename std::tuple_element<0, std::tuple<States...>>::type{}){};
+
+  /// Constructor from an explicit state. The FSM is initialized to this state.
+  /// @param state Initial state for the FSM.
+  FiniteStateMachine(StateVariant state) : m_state(std::move(state)){};
+
+  /// Get the current state of of the FSM (as a variant).
+  /// @return StateVariant The current state of the FSM.
+  const StateVariant& getState() const noexcept { return m_state; }
+
+ public:
+  /// Sets the state to a given one. Triggers `on_exit` and `on_enter` for the
+  /// given states.
+  /// @tparam State Type of the target state
+  /// @tparam Args Additional arguments passed through callback overloads.
+  /// @param state Instance of the target state
+  /// @param args The additional arguments
+  template <typename State, typename... Args>
+  void setState(State state, Args&&... args) {
+    Derived& child = static_cast<Derived&>(*this);
+
+    // call on exit function
+    std::visit([&](auto& s) { child.on_exit(s, std::forward<Args>(args)...); },
+               m_state);
+
+    m_state = std::move(state);
+
+    // call on enter function, the type is known from the template argument.
+    child.on_enter(std::get<State>(m_state), std::forward<Args>(args)...);
+  }
+
+  /// Returns whether the FSM is in the specified state
+  /// @tparam State type to check against
+  /// @param state State instance to check against
+  /// @return Whether the FSM is in the given state.
+  template <typename S>
+  bool is(const S& /*state*/) const noexcept {
+    return is<S>();
+  }
+
+  /// Returns whether the FSM is in the specified state. Alternative version
+  /// directly taking only the template argument.
+  /// @tparam State type to check against
+  /// @return Whether the FSM is in the given state.
+  template <typename S>
+  bool is() const noexcept {
+    if (std::get_if<S>(&m_state)) {
+      return true;
+    }
+    return false;
+  }
+
+  /// Returns whether the FSM is in the terminated state.
+  /// @return Whether the FSM is in the terminated state.
+  bool terminated() const noexcept { return is<Terminated>(); }
+
+ protected:
+  /// Handles processing of an event.
+  /// @note This should only be called from inside the class Deriving from FSM.
+  /// @tparam Event Type of the event being processed
+  /// @tparam Args Arguments being passed to the overload handlers.
+  /// @param event Instance of the event
+  /// @param args Additional arguments
+  /// @return Variant state type, signifying if a transition is supposed to
+  ///         happen.
+  template <typename Event, typename... Args>
+  event_return process_event(Event&& event, Args&&... args) {
+    Derived& child = static_cast<Derived&>(*this);
+
+    child.on_process(event);
+
+    auto new_state = std::visit(
+        [&](auto& s) -> std::optional<StateVariant> {
+          auto s2 = child.on_event(s, std::forward<Event>(event),
+                                   std::forward<Args>(args)...);
+
+          if (s2) {
+            std::visit([&](auto& s2_) { child.on_process(s, event, s2_); },
+                       *s2);
+          } else {
+            child.on_process(s, event);
+          }
+          return std::move(s2);
+        },
+        m_state);
+    return std::move(new_state);
+  }
+
+ public:
+  /// Public interface to handle an event. Will call the appropriate event
+  /// handlers and perform any required transitions.
+  /// @tparam Event Type of the event being triggered
+  /// @tparam Args Additional arguments being passed to overload handlers.
+  /// @param event Instance of the event being triggere
+  /// @param args Additional arguments
+  template <typename Event, typename... Args>
+  void dispatch(Event&& event, Args&&... args) {
+    auto new_state = process_event(std::forward<Event>(event), args...);
+    if (new_state) {
+      std::visit(
+          [&](auto& s) { setState(std::move(s), std::forward<Args>(args)...); },
+          *new_state);
+    }
+  }
+
+ private:
+  StateVariant m_state;
+};
+
+}  // namespace Acts
diff --git a/Tests/Core/Utilities/CMakeLists.txt b/Tests/Core/Utilities/CMakeLists.txt
index e28b06f13b7b61e0a5360b87b36a161c22fc234d..93c74c243bd0f8e53f1839249b0a4b73dc1f129f 100644
--- a/Tests/Core/Utilities/CMakeLists.txt
+++ b/Tests/Core/Utilities/CMakeLists.txt
@@ -97,3 +97,7 @@ target_include_directories (AnnealingUtilityTests PRIVATE ${Boost_INCLUDE_DIRS})
 target_link_libraries (AnnealingUtilityTests PRIVATE ActsCore ActsTestsCommonHelpers ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
 add_test (NAME VertexAnnealingToolUnitTests COMMAND AnnealingUtilityTests)
 acts_add_test_to_cdash_project (PROJECT ACore TEST VertexAnnealingToolUnitTests TARGETS AnnealingUtilityTests)
+
+add_executable (FiniteStateMachineTests FiniteStateMachineTests.cpp)
+target_link_libraries (FiniteStateMachineTests PRIVATE ActsCore ActsTestsCommonHelpers ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY})
+add_test (NAME FiniteStateMachineTests COMMAND FiniteStateMachineTests)
diff --git a/Tests/Core/Utilities/FiniteStateMachineTests.cpp b/Tests/Core/Utilities/FiniteStateMachineTests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2b68b072487181a679b447f362b56ce546ad5172
--- /dev/null
+++ b/Tests/Core/Utilities/FiniteStateMachineTests.cpp
@@ -0,0 +1,291 @@
+// This file is part of the Acts project.
+//
+// Copyright (C) 2019 CERN for the benefit of the Acts project
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// clang-format off
+#define BOOST_TEST_MODULE FSM Tests
+#define BOOST_TEST_DYN_LINK
+#include <boost/test/unit_test.hpp>
+// clang-format on
+
+#include "Acts/Utilities/FiniteStateMachine.hpp"
+
+#include <iostream>
+
+namespace tt = boost::test_tools;
+
+namespace Acts {
+
+namespace Test {
+
+namespace states {
+struct Disconnected {};
+
+struct Connecting {};
+struct Pinging {};
+struct Connected {};
+}  // namespace states
+
+namespace events {
+struct Connect {};
+struct Established {};
+struct Timeout {};
+struct Ping {};
+struct Pong {};
+struct Disconnect {};
+}  // namespace events
+
+struct fsm : FiniteStateMachine<fsm, states::Disconnected, states::Connecting,
+                                states::Pinging, states::Connected> {
+  fsm() : fsm_base(states::Disconnected{}){};
+
+  event_return on_event(const states::Disconnected&, const events::Connect&) {
+    return states::Connecting{};
+  }
+
+  event_return on_event(const states::Connecting&, const events::Established&) {
+    return states::Connected{};
+  }
+
+  event_return on_event(const states::Connected&, const events::Ping&) {
+    std::cout << "ping!" << std::endl;
+    setState(states::Pinging{});
+    return process_event(events::Pong{});
+  }
+
+  event_return on_event(const states::Pinging&, const events::Pong&) {
+    std::cout << "pong!" << std::endl;
+    return states::Connected{};
+  }
+
+  event_return on_event(const states::Connected&, const events::Timeout&) {
+    return states::Connecting{};
+  }
+
+  event_return on_event(const states::Connected&, const events::Disconnect&) {
+    return states::Disconnected{};
+  }
+
+  template <typename State, typename Event>
+  event_return on_event(const State&, const Event&) const {
+    return Terminated{};
+  }
+
+  template <typename State, typename... Args>
+  void on_enter(const State&, Args&&...) {}
+
+  template <typename State, typename... Args>
+  void on_exit(const State&, Args&&...) {}
+
+  template <typename... Args>
+  void on_process(Args&&...) {}
+};
+
+BOOST_AUTO_TEST_SUITE(Utilities)
+
+BOOST_AUTO_TEST_CASE(Transitions) {
+  fsm sm{};
+  BOOST_CHECK(sm.is(states::Disconnected{}));
+  sm.dispatch(events::Connect{});
+  BOOST_CHECK(sm.is(states::Connecting{}));
+  sm.dispatch(events::Established{});
+  BOOST_CHECK(sm.is(states::Connected{}));
+  sm.dispatch(events::Ping{});
+  sm.dispatch(events::Ping{});
+  sm.dispatch(events::Ping{});
+  sm.dispatch(events::Ping{});
+  BOOST_CHECK(sm.is(states::Connected{}));
+  sm.dispatch(events::Timeout{});
+  BOOST_CHECK(sm.is(states::Connecting{}));
+  sm.dispatch(events::Established{});
+  BOOST_CHECK(sm.is(states::Connected{}));
+  sm.dispatch(events::Disconnect{});
+  BOOST_CHECK(sm.is(states::Disconnected{}));
+}
+
+BOOST_AUTO_TEST_CASE(Terminted) {
+  fsm sm{};
+  BOOST_CHECK(sm.is(states::Disconnected{}));
+
+  sm.dispatch(events::Disconnect{});
+  BOOST_CHECK(sm.terminated());
+}
+
+struct fsm2
+    : FiniteStateMachine<fsm2, states::Disconnected, states::Connected> {
+  fsm2() : fsm_base(states::Disconnected{}){};
+
+  event_return on_event(const states::Disconnected&, const events::Connect&,
+                        double f) {
+    std::cout << "f: " << f << std::endl;
+    return states::Connected{};
+  }
+
+  event_return on_event(const states::Connected&, const events::Disconnect&) {
+    std::cout << "disconnect!" << std::endl;
+    return states::Disconnected{};
+  }
+
+  template <typename State, typename Event, typename... Args>
+  event_return on_event(const State&, const Event&, Args&&...) const {
+    return Terminated{};
+  }
+
+  template <typename... Args>
+  void on_enter(const Terminated&, Args&&...) {
+    throw std::runtime_error("FSM terminated!");
+  }
+
+  template <typename State, typename... Args>
+  void on_enter(const State&, Args&&...) {}
+
+  template <typename State, typename... Args>
+  void on_exit(const State&, Args&&...) {}
+  template <typename... Args>
+  void on_process(Args&&...) {}
+};
+
+BOOST_AUTO_TEST_CASE(Arguments) {
+  fsm2 sm{};
+  BOOST_CHECK(sm.is(states::Disconnected{}));
+
+  sm.dispatch(events::Connect{}, 42.);
+  BOOST_CHECK(sm.is(states::Connected{}));
+  sm.dispatch(events::Disconnect{});
+  BOOST_CHECK(sm.is(states::Disconnected{}));
+  sm.dispatch(events::Connect{}, -1.);
+
+  // call disconnect, but disconnect does not accept this call signature
+  BOOST_REQUIRE_THROW(sm.dispatch(events::Disconnect{}, 9), std::runtime_error);
+  BOOST_CHECK(sm.terminated());
+
+  // cannot dispatch on terminated (in this specific configuration, in
+  // general terminated is just another state).
+  BOOST_REQUIRE_THROW(sm.dispatch(events::Connect{}), std::runtime_error);
+  // still in terminated
+  BOOST_CHECK(sm.terminated());
+
+  // we can reset the state though!
+  sm.setState(states::Disconnected{});
+  BOOST_CHECK(sm.is(states::Disconnected{}));
+  sm.dispatch(events::Connect{}, -1.);
+  BOOST_CHECK(sm.is(states::Connected{}));
+}
+
+struct S1 {};
+struct S2 {};
+struct S3 {};
+
+struct E1 {};
+struct E2 {};
+struct E3 {};
+
+struct fsm3 : FiniteStateMachine<fsm3, S1, S2, S3> {
+  bool on_exit_called = false;
+  bool on_enter_called = false;
+  bool on_process_called = false;
+  void reset() {
+    on_exit_called = false;
+    on_enter_called = false;
+    on_process_called = false;
+  }
+
+  // S1 + E1 = S2
+  event_return on_event(const S1&, const E1&) { return S2{}; }
+
+  // S2 + E1 = S2
+  // external transition to self
+  event_return on_event(const S2&, const E1&) { return S2{}; }
+
+  // S2 + E2
+  // internal transition
+  event_return on_event(const S2&, const E2&) {
+    return std::nullopt;
+    // return S2{};
+  }
+
+  // S2 + E3 = S3
+  // external transition
+  event_return on_event(const S2&, const E3&) { return S3{}; }
+
+  // catchers
+
+  template <typename State, typename Event, typename... Args>
+  event_return on_event(const State&, const Event&, Args&&...) const {
+    return Terminated{};
+  }
+
+  template <typename State, typename... Args>
+  void on_enter(const State&, Args&&...) {
+    on_enter_called = true;
+  }
+
+  template <typename State, typename... Args>
+  void on_exit(const State&, Args&&...) {
+    on_exit_called = true;
+  }
+
+  template <typename... Args>
+  void on_process(Args&&...) {
+    on_process_called = true;
+  }
+};
+
+BOOST_AUTO_TEST_CASE(InternalTransitions) {
+  fsm3 sm;
+  BOOST_CHECK(sm.is(S1{}));
+
+  sm.dispatch(E1{});
+  BOOST_CHECK(sm.is(S2{}));
+  BOOST_CHECK(sm.on_exit_called);
+  BOOST_CHECK(sm.on_enter_called);
+  BOOST_CHECK(sm.on_process_called);
+
+  sm.reset();
+
+  sm.dispatch(E1{});
+  // still in S2
+  BOOST_CHECK(sm.is(S2{}));
+  // on_enter / exit should have been called
+  BOOST_CHECK(sm.on_exit_called);
+  BOOST_CHECK(sm.on_enter_called);
+  BOOST_CHECK(sm.on_process_called);
+  sm.reset();
+
+  sm.dispatch(E2{});
+  // still in S2
+  BOOST_CHECK(sm.is(S2{}));
+  // on_enter / exit should NOT have been called
+  BOOST_CHECK(!sm.on_exit_called);
+  BOOST_CHECK(!sm.on_enter_called);
+  BOOST_CHECK(sm.on_process_called);
+  sm.reset();
+
+  sm.dispatch(E3{});
+  BOOST_CHECK(sm.is(S3{}));
+  // on_enter / exit should have been called
+  BOOST_CHECK(sm.on_exit_called);
+  BOOST_CHECK(sm.on_enter_called);
+  BOOST_CHECK(sm.on_process_called);
+
+  sm.setState(S1{});
+  sm.reset();
+  BOOST_CHECK(sm.is(S1{}));
+  // dispatch invalid event
+  sm.dispatch(E3{});
+  // should be terminated now
+  BOOST_CHECK(sm.terminated());
+  // hooks should have fired
+  BOOST_CHECK(sm.on_exit_called);
+  BOOST_CHECK(sm.on_enter_called);
+  BOOST_CHECK(sm.on_process_called);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}  // namespace Test
+}  // namespace Acts