diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index 7f85103816eb79fa9b363975f85c5929a3c964c8..038338e7024d318dba20459e56fdf6171e85b969 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -11,13 +11,13 @@ class BroadcastMessagesController < ApplicationController
     urgency :low
 
     def index
-      @broadcast_message = BroadcastMessage.new
+      @broadcast_message = System::BroadcastMessage.new
     end
 
     def edit; end
 
     def create
-      @broadcast_message = BroadcastMessage.new(broadcast_message_params)
+      @broadcast_message = System::BroadcastMessage.new(broadcast_message_params)
       success = @broadcast_message.save
 
       respond_to do |format|
@@ -69,18 +69,18 @@ def destroy
     end
 
     def preview
-      @broadcast_message = BroadcastMessage.new(broadcast_message_params)
+      @broadcast_message = System::BroadcastMessage.new(broadcast_message_params)
       render plain: render_broadcast_message(@broadcast_message), status: :ok
     end
 
     protected
 
     def find_broadcast_message
-      @broadcast_message = BroadcastMessage.find(params[:id])
+      @broadcast_message = System::BroadcastMessage.find(params[:id])
     end
 
     def find_broadcast_messages
-      @broadcast_messages = BroadcastMessage.order(ends_at: :desc).page(params[:page]) # rubocop: disable CodeReuse/ActiveRecord
+      @broadcast_messages = System::BroadcastMessage.order(ends_at: :desc).page(params[:page]) # rubocop: disable CodeReuse/ActiveRecord
     end
 
     def broadcast_message_params
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index a62ffa144f1c921b57408a1c2cecdaba7e27d9ea..f04694a3aa98411c2e1bb4046c4c4796ff52ac86 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -4,7 +4,7 @@ module BroadcastMessagesHelper
   include Gitlab::Utils::StrongMemoize
 
   def current_broadcast_banner_messages
-    BroadcastMessage.current_banner_messages(
+    System::BroadcastMessage.current_banner_messages(
       current_path: request.path,
       user_access_level: current_user_access_level_for_project_or_group
     ).select do |message|
@@ -13,7 +13,7 @@ def current_broadcast_banner_messages
   end
 
   def current_broadcast_notification_message
-    not_hidden_messages = BroadcastMessage.current_notification_messages(
+    not_hidden_messages = System::BroadcastMessage.current_notification_messages(
       current_path: request.path,
       user_access_level: current_user_access_level_for_project_or_group
     ).select do |message|
@@ -51,7 +51,7 @@ def render_broadcast_message(broadcast_message)
   end
 
   def target_access_level_options
-    BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS.map do |access_level|
+    System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS.map do |access_level|
       [Gitlab::Access.human_access(access_level), access_level]
     end
   end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
deleted file mode 100644
index ccc5ca7395dcc36620ac62de4d659b0c33f9176b..0000000000000000000000000000000000000000
--- a/app/models/broadcast_message.rb
+++ /dev/null
@@ -1,163 +0,0 @@
-# frozen_string_literal: true
-
-class BroadcastMessage < MainClusterwide::ApplicationRecord
-  include CacheMarkdownField
-  include Sortable
-
-  ALLOWED_TARGET_ACCESS_LEVELS = [
-    Gitlab::Access::GUEST,
-    Gitlab::Access::REPORTER,
-    Gitlab::Access::DEVELOPER,
-    Gitlab::Access::MAINTAINER,
-    Gitlab::Access::OWNER
-  ].freeze
-
-  cache_markdown_field :message, pipeline: :broadcast_message, whitelisted: true
-
-  validates :message,   presence: true
-  validates :starts_at, presence: true
-  validates :ends_at,   presence: true
-  validates :broadcast_type, presence: true
-  validates :target_access_levels, inclusion: { in: ALLOWED_TARGET_ACCESS_LEVELS }
-  validates :show_in_cli, allow_nil: false, inclusion: { in: [true, false], message: N_('must be a boolean value') }
-
-  validates :color, allow_blank: true, color: true
-  validates :font,  allow_blank: true, color: true
-
-  attribute :color, default: '#E75E40'
-  attribute :font, default: '#FFFFFF'
-
-  scope :current_and_future_messages, -> { where('ends_at > :now', now: Time.current).order_id_asc }
-
-  CACHE_KEY = 'broadcast_message_current_json'
-  BANNER_CACHE_KEY = 'broadcast_message_current_banner_json'
-  NOTIFICATION_CACHE_KEY = 'broadcast_message_current_notification_json'
-
-  after_commit :flush_redis_cache
-
-  enum theme: {
-    indigo: 0,
-    'light-indigo': 1,
-    blue: 2,
-    'light-blue': 3,
-    green: 4,
-    'light-green': 5,
-    red: 6,
-    'light-red': 7,
-    dark: 8,
-    light: 9
-  }, _default: 0, _prefix: true
-
-  enum broadcast_type: {
-    banner: 1,
-    notification: 2
-  }
-
-  class << self
-    def current_banner_messages(current_path: nil, user_access_level: nil)
-      fetch_messages BANNER_CACHE_KEY, current_path, user_access_level do
-        current_and_future_messages.banner
-      end
-    end
-
-    def current_show_in_cli_banner_messages
-      current_banner_messages.select(&:show_in_cli?)
-    end
-
-    def current_notification_messages(current_path: nil, user_access_level: nil)
-      fetch_messages NOTIFICATION_CACHE_KEY, current_path, user_access_level do
-        current_and_future_messages.notification
-      end
-    end
-
-    def current(current_path: nil, user_access_level: nil)
-      fetch_messages CACHE_KEY, current_path, user_access_level do
-        current_and_future_messages
-      end
-    end
-
-    def cache
-      ::Gitlab::SafeRequestStore.fetch(:broadcast_message_json_cache) do
-        Gitlab::Cache::JsonCaches::JsonKeyed.new
-      end
-    end
-
-    def cache_expires_in
-      2.weeks
-    end
-
-    private
-
-    def fetch_messages(cache_key, current_path, user_access_level, &block)
-      messages = cache.fetch(cache_key, as: BroadcastMessage, expires_in: cache_expires_in, &block)
-
-      now_or_future = messages.select(&:now_or_future?)
-
-      # If there are cached entries but they don't match the ones we are
-      # displaying we'll refresh the cache so we don't need to keep filtering.
-      cache.expire(cache_key) if now_or_future != messages
-
-      messages = now_or_future.select(&:now?)
-      messages = messages.select do |message|
-        message.matches_current_user_access_level?(user_access_level)
-      end
-      messages.select do |message|
-        message.matches_current_path(current_path)
-      end
-    end
-  end
-
-  def active?
-    started? && !ended?
-  end
-
-  def started?
-    Time.current >= starts_at
-  end
-
-  def ended?
-    ends_at < Time.current
-  end
-
-  def now?
-    (starts_at..ends_at).cover?(Time.current)
-  end
-
-  def future?
-    starts_at > Time.current
-  end
-
-  def now_or_future?
-    now? || future?
-  end
-
-  def matches_current_user_access_level?(user_access_level)
-    return true unless target_access_levels.present?
-
-    target_access_levels.include? user_access_level
-  end
-
-  def matches_current_path(current_path)
-    return false if current_path.blank? && target_path.present?
-    return true if current_path.blank? || target_path.blank?
-
-    # Ensure paths are consistent across callers.
-    # This fixes a mismatch between requests in the GUI and CLI
-    #
-    # This has to be reassigned due to frozen strings being provided.
-    current_path = "/#{current_path}" unless current_path.start_with?("/")
-
-    escaped = Regexp.escape(target_path).gsub('\\*', '.*')
-    regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
-
-    regexp.match(current_path)
-  end
-
-  def flush_redis_cache
-    [CACHE_KEY, BANNER_CACHE_KEY, NOTIFICATION_CACHE_KEY].each do |key|
-      self.class.cache.expire(key)
-    end
-  end
-end
-
-BroadcastMessage.prepend_mod_with('BroadcastMessage')
diff --git a/app/models/system/broadcast_message.rb b/app/models/system/broadcast_message.rb
new file mode 100644
index 0000000000000000000000000000000000000000..332baea44490ea81700b4b8f96ae0aebe1a5903c
--- /dev/null
+++ b/app/models/system/broadcast_message.rb
@@ -0,0 +1,165 @@
+# frozen_string_literal: true
+
+module System
+  class BroadcastMessage < MainClusterwide::ApplicationRecord
+    include CacheMarkdownField
+    include Sortable
+
+    ALLOWED_TARGET_ACCESS_LEVELS = [
+      Gitlab::Access::GUEST,
+      Gitlab::Access::REPORTER,
+      Gitlab::Access::DEVELOPER,
+      Gitlab::Access::MAINTAINER,
+      Gitlab::Access::OWNER
+    ].freeze
+
+    cache_markdown_field :message, pipeline: :broadcast_message, whitelisted: true
+
+    validates :message,   presence: true
+    validates :starts_at, presence: true
+    validates :ends_at,   presence: true
+    validates :broadcast_type, presence: true
+    validates :target_access_levels, inclusion: { in: ALLOWED_TARGET_ACCESS_LEVELS }
+    validates :show_in_cli, allow_nil: false, inclusion: { in: [true, false], message: N_('must be a boolean value') }
+
+    validates :color, allow_blank: true, color: true
+    validates :font,  allow_blank: true, color: true
+
+    attribute :color, default: '#E75E40'
+    attribute :font, default: '#FFFFFF'
+
+    scope :current_and_future_messages, -> { where('ends_at > :now', now: Time.current).order_id_asc }
+
+    CACHE_KEY = 'broadcast_message_current_json'
+    BANNER_CACHE_KEY = 'broadcast_message_current_banner_json'
+    NOTIFICATION_CACHE_KEY = 'broadcast_message_current_notification_json'
+
+    after_commit :flush_redis_cache
+
+    enum theme: {
+      indigo: 0,
+      'light-indigo': 1,
+      blue: 2,
+      'light-blue': 3,
+      green: 4,
+      'light-green': 5,
+      red: 6,
+      'light-red': 7,
+      dark: 8,
+      light: 9
+    }, _default: 0, _prefix: true
+
+    enum broadcast_type: {
+      banner: 1,
+      notification: 2
+    }
+
+    class << self
+      def current_banner_messages(current_path: nil, user_access_level: nil)
+        fetch_messages BANNER_CACHE_KEY, current_path, user_access_level do
+          current_and_future_messages.banner
+        end
+      end
+
+      def current_show_in_cli_banner_messages
+        current_banner_messages.select(&:show_in_cli?)
+      end
+
+      def current_notification_messages(current_path: nil, user_access_level: nil)
+        fetch_messages NOTIFICATION_CACHE_KEY, current_path, user_access_level do
+          current_and_future_messages.notification
+        end
+      end
+
+      def current(current_path: nil, user_access_level: nil)
+        fetch_messages CACHE_KEY, current_path, user_access_level do
+          current_and_future_messages
+        end
+      end
+
+      def cache
+        ::Gitlab::SafeRequestStore.fetch(:broadcast_message_json_cache) do
+          Gitlab::Cache::JsonCaches::JsonKeyed.new
+        end
+      end
+
+      def cache_expires_in
+        2.weeks
+      end
+
+      private
+
+      def fetch_messages(cache_key, current_path, user_access_level, &block)
+        messages = cache.fetch(cache_key, as: System::BroadcastMessage, expires_in: cache_expires_in, &block)
+
+        now_or_future = messages.select(&:now_or_future?)
+
+        # If there are cached entries but they don't match the ones we are
+        # displaying we'll refresh the cache so we don't need to keep filtering.
+        cache.expire(cache_key) if now_or_future != messages
+
+        messages = now_or_future.select(&:now?)
+        messages = messages.select do |message|
+          message.matches_current_user_access_level?(user_access_level)
+        end
+        messages.select do |message|
+          message.matches_current_path(current_path)
+        end
+      end
+    end
+
+    def active?
+      started? && !ended?
+    end
+
+    def started?
+      Time.current >= starts_at
+    end
+
+    def ended?
+      ends_at < Time.current
+    end
+
+    def now?
+      (starts_at..ends_at).cover?(Time.current)
+    end
+
+    def future?
+      starts_at > Time.current
+    end
+
+    def now_or_future?
+      now? || future?
+    end
+
+    def matches_current_user_access_level?(user_access_level)
+      return true unless target_access_levels.present?
+
+      target_access_levels.include? user_access_level
+    end
+
+    def matches_current_path(current_path)
+      return false if current_path.blank? && target_path.present?
+      return true if current_path.blank? || target_path.blank?
+
+      # Ensure paths are consistent across callers.
+      # This fixes a mismatch between requests in the GUI and CLI
+      #
+      # This has to be reassigned due to frozen strings being provided.
+      current_path = "/#{current_path}" unless current_path.start_with?("/")
+
+      escaped = Regexp.escape(target_path).gsub('\\*', '.*')
+      regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
+
+      regexp.match(current_path)
+    end
+
+    def flush_redis_cache
+      [CACHE_KEY, BANNER_CACHE_KEY, NOTIFICATION_CACHE_KEY].each do |key|
+        self.class.cache.expire(key)
+      end
+    end
+  end
+end
+
+System::BroadcastMessage.prepend_mod_with('System::BroadcastMessage')
diff --git a/app/services/post_receive_service.rb b/app/services/post_receive_service.rb
index 5ab5732ecf5408be4fc7b23a385470735b477a62..7cf1855988ed894d2ee3ab163439bb4c0319df56 100644
--- a/app/services/post_receive_service.rb
+++ b/app/services/post_receive_service.rb
@@ -87,14 +87,14 @@ def broadcast_message
 
     if project
       scoped_messages =
-        BroadcastMessage.current_banner_messages(current_path: project.full_path).select do |message|
+        System::BroadcastMessage.current_banner_messages(current_path: project.full_path).select do |message|
           message.target_path.present? && message.matches_current_path(project.full_path) && message.show_in_cli?
         end
 
       banner = scoped_messages.last
     end
 
-    banner ||= BroadcastMessage.current_show_in_cli_banner_messages.last
+    banner ||= System::BroadcastMessage.current_show_in_cli_banner_messages.last
 
     banner&.message
   end
diff --git a/db/docs/broadcast_messages.yml b/db/docs/broadcast_messages.yml
index bceb79b7f9ce67a55040d7d6ec1e23d10212bf82..c6c28964e5200b4b6bcd509c9917e51d28454c3e 100644
--- a/db/docs/broadcast_messages.yml
+++ b/db/docs/broadcast_messages.yml
@@ -1,7 +1,7 @@
 ---
 table_name: broadcast_messages
 classes:
-- BroadcastMessage
+- System::BroadcastMessage
 feature_categories:
 - onboarding
 description: GitLab can display broadcast messages to users of a GitLab instance
diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md
index 2ed4d93402217ac121bb582afc021e3e0b54e432..8efcd3c44c1fdf04c20b507de0cee8aa4dce124d 100644
--- a/doc/development/api_styleguide.md
+++ b/doc/development/api_styleguide.md
@@ -51,7 +51,7 @@ params do
   optional :per_page, type: Integer, desc: 'Number of messages per page'
 end
 get do
-  messages = BroadcastMessage.all
+  messages = System::BroadcastMessage.all
 
   present paginate(messages), with: Entities::BroadcastMessage
 end
diff --git a/ee/app/models/ee/broadcast_message.rb b/ee/app/models/ee/broadcast_message.rb
deleted file mode 100644
index 6847027c7e4088d10ca09e079f09832b0e0e5197..0000000000000000000000000000000000000000
--- a/ee/app/models/ee/broadcast_message.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module EE
-  # BroadcastMessage EE mixin
-  #
-  # This module is intended to encapsulate EE-specific model logic
-  # and be prepended in the `BroadcastMessage` model
-  module BroadcastMessage
-    extend ActiveSupport::Concern
-
-    class_methods do
-      extend ::Gitlab::Utils::Override
-
-      override :cache_expires_in
-      def cache_expires_in
-        if ::Gitlab::Geo.secondary?
-          30.seconds
-        else
-          super
-        end
-      end
-    end
-  end
-end
diff --git a/ee/app/models/ee/system/broadcast_message.rb b/ee/app/models/ee/system/broadcast_message.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a636b52df749244c217c244a914611da85f1b567
--- /dev/null
+++ b/ee/app/models/ee/system/broadcast_message.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module EE
+  module System
+    # BroadcastMessage EE mixin
+    #
+    # This module is intended to encapsulate EE-specific model logic
+    # and be prepended in the `BroadcastMessage` model
+    module BroadcastMessage
+      extend ActiveSupport::Concern
+
+      class_methods do
+        extend ::Gitlab::Utils::Override
+
+        override :cache_expires_in
+        def cache_expires_in
+          if ::Gitlab::Geo.secondary?
+            30.seconds
+          else
+            super
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/models/broadcast_message_spec.rb b/ee/spec/models/ee/system/broadcast_message_spec.rb
similarity index 95%
rename from ee/spec/models/broadcast_message_spec.rb
rename to ee/spec/models/ee/system/broadcast_message_spec.rb
index 6382b08d7d881720e99e4d9d6bd45c797a60d26e..08d868c2f110719a1bf6ee4abd7373c16893de93 100644
--- a/ee/spec/models/broadcast_message_spec.rb
+++ b/ee/spec/models/ee/system/broadcast_message_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe BroadcastMessage do
+RSpec.describe System::BroadcastMessage, feature_category: :onboarding do
   subject { build(:broadcast_message) }
 
   describe '.current', :use_clean_rails_memory_store_caching do
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
index 6af7c3b4804a2490960c555792aa017aae823fa8..15d64e5337cfa0b48e991cc0e6e49f2630c9b191 100644
--- a/lib/api/broadcast_messages.rb
+++ b/lib/api/broadcast_messages.rb
@@ -10,7 +10,7 @@ class BroadcastMessages < ::API::Base
     resource :broadcast_messages do
       helpers do
         def find_message
-          BroadcastMessage.find(params[:id])
+          System::BroadcastMessage.find(params[:id])
         end
       end
 
@@ -22,7 +22,7 @@ def find_message
         use :pagination
       end
       get do
-        messages = BroadcastMessage.all.order_id_desc
+        messages = System::BroadcastMessage.all.order_id_desc
 
         present paginate(messages), with: Entities::BroadcastMessage
       end
@@ -40,16 +40,16 @@ def find_message
         optional :target_access_levels,
                  type: Array[Integer],
                  coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce,
-                 values: BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS,
+                 values: System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS,
                  desc: 'Target user roles'
         optional :target_path, type: String, desc: 'Target path'
-        optional :broadcast_type, type: String, values: BroadcastMessage.broadcast_types.keys, desc: 'Broadcast type. Defaults to banner', default: -> { 'banner' }
+        optional :broadcast_type, type: String, values: System::BroadcastMessage.broadcast_types.keys, desc: 'Broadcast type. Defaults to banner', default: -> { 'banner' }
         optional :dismissable, type: Boolean, desc: 'Is dismissable'
       end
       post do
         authenticated_as_admin!
 
-        message = BroadcastMessage.create(declared_params(include_missing: false))
+        message = System::BroadcastMessage.create(declared_params(include_missing: false))
 
         if message.persisted?
           present message, with: Entities::BroadcastMessage
@@ -85,10 +85,10 @@ def find_message
         optional :target_access_levels,
                  type: Array[Integer],
                  coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce,
-                 values: BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS,
+                 values: System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS,
                  desc: 'Target user roles'
         optional :target_path, type: String, desc: 'Target path'
-        optional :broadcast_type, type: String, values: BroadcastMessage.broadcast_types.keys, desc: 'Broadcast Type'
+        optional :broadcast_type, type: String, values: System::BroadcastMessage.broadcast_types.keys, desc: 'Broadcast Type'
         optional :dismissable, type: Boolean, desc: 'Is dismissable'
       end
       put ':id' do
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/system/broadcast_messages.rb
similarity index 87%
rename from spec/factories/broadcast_messages.rb
rename to spec/factories/system/broadcast_messages.rb
index 0602ce31136ba55b08bd4439b8fea14aa59659a0..4742d183ab75e2d906c0851865b0afa42452a4d1 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/system/broadcast_messages.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 FactoryBot.define do
-  factory :broadcast_message do
+  factory :broadcast_message, class: 'System::BroadcastMessage' do
     message { "MyText" }
     starts_at { 1.day.ago }
     ends_at { 1.day.from_now }
diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb
index 05e745e249e5446ac44f2d3fcdb06d82115003e7..90c000aea5742261ebe32b09e941dfd23220f4b6 100644
--- a/spec/helpers/broadcast_messages_helper_spec.rb
+++ b/spec/helpers/broadcast_messages_helper_spec.rb
@@ -102,7 +102,7 @@
   end
 
   describe '#broadcast_message' do
-    let(:current_broadcast_message) { BroadcastMessage.new(message: 'Current Message') }
+    let(:current_broadcast_message) { System::BroadcastMessage.new(message: 'Current Message') }
 
     it 'returns nil when no current message' do
       expect(helper.broadcast_message(nil)).to be_nil
diff --git a/spec/lib/gitlab/cache/json_caches/json_keyed_spec.rb b/spec/lib/gitlab/cache/json_caches/json_keyed_spec.rb
index c4ec393c3acf9cce4d22fb3dae7364bd79ae3d45..8afd5c2bfcd95df655026ed3c3a9cb868b10142a 100644
--- a/spec/lib/gitlab/cache/json_caches/json_keyed_spec.rb
+++ b/spec/lib/gitlab/cache/json_caches/json_keyed_spec.rb
@@ -51,7 +51,7 @@
           current_cache = { '_other_revision_' => '_other_value_' }.merge(nested_cache_result).to_json
           allow(backend).to receive(:read).with(expanded_key).and_return(current_cache)
 
-          expect(cache.read(key, BroadcastMessage)).to eq(broadcast_message)
+          expect(cache.read(key, System::BroadcastMessage)).to eq(broadcast_message)
         end
       end
     end
diff --git a/spec/lib/gitlab/cache/json_caches/redis_keyed_spec.rb b/spec/lib/gitlab/cache/json_caches/redis_keyed_spec.rb
index 6e98cdd74ceea02cb5f63776bc7c492a736bf0b5..f408bbf8d25c44c79118d4b69e3be192656dbf1f 100644
--- a/spec/lib/gitlab/cache/json_caches/redis_keyed_spec.rb
+++ b/spec/lib/gitlab/cache/json_caches/redis_keyed_spec.rb
@@ -21,7 +21,7 @@
         allow(backend).to receive(:read).with(expanded_key).and_return(true)
 
         expect(Gitlab::Json).to receive(:parse).with("true").and_call_original
-        expect(cache.read(key, BroadcastMessage)).to eq(true)
+        expect(cache.read(key, System::BroadcastMessage)).to eq(true)
       end
     end
 
@@ -30,7 +30,7 @@
         allow(backend).to receive(:read).with(expanded_key).and_return(false)
 
         expect(Gitlab::Json).to receive(:parse).with("false").and_call_original
-        expect(cache.read(key, BroadcastMessage)).to eq(false)
+        expect(cache.read(key, System::BroadcastMessage)).to eq(false)
       end
     end
   end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/system/broadcast_message_spec.rb
similarity index 99%
rename from spec/models/broadcast_message_spec.rb
rename to spec/models/system/broadcast_message_spec.rb
index 7485496cf903a919a1a0f8a7329205cec90577e2..b50dfc35eea2c78b553641cbdd5edd85f1a987a7 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/system/broadcast_message_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe BroadcastMessage, feature_category: :onboarding do
+RSpec.describe System::BroadcastMessage, feature_category: :onboarding do
   subject { build(:broadcast_message) }
 
   it { is_expected.to be_valid }
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index 01bb8101f76f4ca7e0c1aefca47a428c37092694..1d1d66ad125b39e1d7fe737e73aa59e4ecee16be 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -229,7 +229,7 @@
           expect(data.stringify_keys).not_to include('meta.project', 'meta.root_namespace', 'meta.user')
         end
 
-        expect(BroadcastMessage).to receive(:all).and_raise('An error!')
+        expect(System::BroadcastMessage).to receive(:all).and_raise('An error!')
 
         get(api('/broadcast_messages'))
 
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index 530c81364a80ee15c041041c8b4c676d9e73efdc..d063f290e8d18664776b05ce0fd664b818acd725 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -246,7 +246,7 @@
         delete api(path, admin, admin_mode: true)
 
         expect(response).to have_gitlab_http_status(:no_content)
-      end.to change { BroadcastMessage.count }.by(-1)
+      end.to change { System::BroadcastMessage.count }.by(-1)
     end
   end
 end
diff --git a/spec/services/post_receive_service_spec.rb b/spec/services/post_receive_service_spec.rb
index 20d86f74f8681134421d869679364b45a19c0703..167baed06e77cbde8211cec34e44702b23e111ed 100644
--- a/spec/services/post_receive_service_spec.rb
+++ b/spec/services/post_receive_service_spec.rb
@@ -243,7 +243,7 @@
 
   context 'nil broadcast message' do
     it 'does not output a broadcast message' do
-      allow(BroadcastMessage).to receive(:current).and_return(nil)
+      allow(System::BroadcastMessage).to receive(:current).and_return(nil)
 
       expect(has_alert_messages?(subject)).to be_falsey
     end
diff --git a/spec/support/shared_examples/lib/gitlab/cache/json_cache_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cache/json_cache_shared_examples.rb
index 0472bb87e622e2d40e0ccedeec84ffefa787c2ea..f60974beaf8f44a551700865003e8c00d66740e8 100644
--- a/spec/support/shared_examples/lib/gitlab/cache/json_cache_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cache/json_cache_shared_examples.rb
@@ -18,7 +18,7 @@
     it 'parses the cached value' do
       allow(backend).to receive(:read).with(expanded_key).and_return(json_value(broadcast_message))
 
-      expect(cache.read(key, BroadcastMessage)).to eq(broadcast_message)
+      expect(cache.read(key, System::BroadcastMessage)).to eq(broadcast_message)
     end
 
     it 'returns nil when klass is nil' do
@@ -30,14 +30,14 @@
     it 'gracefully handles an empty hash' do
       allow(backend).to receive(:read).with(expanded_key).and_return(json_value({}))
 
-      expect(cache.read(key, BroadcastMessage)).to be_a(BroadcastMessage)
+      expect(cache.read(key, System::BroadcastMessage)).to be_a(System::BroadcastMessage)
     end
 
     context 'when the cached value is a JSON true value' do
       it 'parses the cached value' do
         allow(backend).to receive(:read).with(expanded_key).and_return(json_value(true))
 
-        expect(cache.read(key, BroadcastMessage)).to eq(true)
+        expect(cache.read(key, System::BroadcastMessage)).to eq(true)
       end
     end
 
@@ -45,7 +45,7 @@
       it 'parses the cached value' do
         allow(backend).to receive(:read).with(expanded_key).and_return(json_value(false))
 
-        expect(cache.read(key, BroadcastMessage)).to eq(false)
+        expect(cache.read(key, System::BroadcastMessage)).to eq(false)
       end
     end
 
@@ -53,23 +53,23 @@
       it 'gracefully handles bad cached entry' do
         allow(backend).to receive(:read).with(expanded_key).and_return('{')
 
-        expect(cache.read(key, BroadcastMessage)).to be_nil
+        expect(cache.read(key, System::BroadcastMessage)).to be_nil
       end
 
       it 'gracefully handles unknown attributes' do
         read_value = json_value(broadcast_message.attributes.merge(unknown_attribute: 1))
         allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
 
-        expect(cache.read(key, BroadcastMessage)).to be_nil
+        expect(cache.read(key, System::BroadcastMessage)).to be_nil
       end
 
       it 'gracefully handles excluded fields from attributes during serialization' do
         read_value = json_value(broadcast_message.attributes.except("message_html"))
         allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
 
-        result = cache.read(key, BroadcastMessage)
+        result = cache.read(key, System::BroadcastMessage)
 
-        BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+        System::BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
           expect(result.public_send(field)).to be_nil
         end
       end
@@ -79,7 +79,7 @@
       it 'parses the cached value' do
         allow(backend).to receive(:read).with(expanded_key).and_return(json_value([broadcast_message]))
 
-        expect(cache.read(key, BroadcastMessage)).to eq([broadcast_message])
+        expect(cache.read(key, System::BroadcastMessage)).to eq([broadcast_message])
       end
 
       it 'returns an empty array when klass is nil' do
@@ -91,20 +91,20 @@
       it 'gracefully handles bad cached entry' do
         allow(backend).to receive(:read).with(expanded_key).and_return('[')
 
-        expect(cache.read(key, BroadcastMessage)).to be_nil
+        expect(cache.read(key, System::BroadcastMessage)).to be_nil
       end
 
       it 'gracefully handles an empty array' do
         allow(backend).to receive(:read).with(expanded_key).and_return(json_value([]))
 
-        expect(cache.read(key, BroadcastMessage)).to eq([])
+        expect(cache.read(key, System::BroadcastMessage)).to eq([])
       end
 
       it 'gracefully handles items with unknown attributes' do
         read_value = json_value([{ unknown_attribute: 1 }, broadcast_message.attributes])
         allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
 
-        expect(cache.read(key, BroadcastMessage)).to eq([broadcast_message])
+        expect(cache.read(key, System::BroadcastMessage)).to eq([broadcast_message])
       end
     end
   end
@@ -206,20 +206,20 @@
         end
 
         it 'parses the cached value' do
-          result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+          result = cache.fetch(key, as: System::BroadcastMessage) { 'block result' }
 
           expect(result).to eq(broadcast_message)
         end
 
         it 'decodes enums correctly' do
-          result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+          result = cache.fetch(key, as: System::BroadcastMessage) { 'block result' }
 
           expect(result.broadcast_type).to eq(broadcast_message.broadcast_type)
         end
 
         context 'when the cached value is an instance of ActiveRecord::Base' do
           it 'returns a persisted record when id is set' do
-            result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+            result = cache.fetch(key, as: System::BroadcastMessage) { 'block result' }
 
             expect(result).to be_persisted
           end
@@ -227,7 +227,7 @@
           it 'returns a new record when id is nil' do
             backend.write(expanded_key, json_value(build(:broadcast_message)))
 
-            result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+            result = cache.fetch(key, as: System::BroadcastMessage) { 'block result' }
 
             expect(result).to be_new_record
           end
@@ -235,7 +235,7 @@
           it 'returns a new record when id is missing' do
             backend.write(expanded_key, json_value(build(:broadcast_message).attributes.except('id')))
 
-            result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+            result = cache.fetch(key, as: System::BroadcastMessage) { 'block result' }
 
             expect(result).to be_new_record
           end
@@ -243,7 +243,7 @@
           it 'gracefully handles bad cached entry' do
             allow(backend).to receive(:read).with(expanded_key).and_return('{')
 
-            result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+            result = cache.fetch(key, as: System::BroadcastMessage) { 'block result' }
 
             expect(result).to eq 'block result'
           end
@@ -251,14 +251,14 @@
           it 'gracefully handles an empty hash' do
             allow(backend).to receive(:read).with(expanded_key).and_return(json_value({}))
 
-            expect(cache.fetch(key, as: BroadcastMessage)).to be_a(BroadcastMessage)
+            expect(cache.fetch(key, as: System::BroadcastMessage)).to be_a(System::BroadcastMessage)
           end
 
           it 'gracefully handles unknown attributes' do
             read_value = json_value(broadcast_message.attributes.merge(unknown_attribute: 1))
             allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
 
-            result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+            result = cache.fetch(key, as: System::BroadcastMessage) { 'block result' }
 
             expect(result).to eq 'block result'
           end
@@ -267,9 +267,9 @@
             read_value = json_value(broadcast_message.attributes.except("message_html"))
             allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
 
-            result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+            result = cache.fetch(key, as: System::BroadcastMessage) { 'block result' }
 
-            BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+            System::BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
               expect(result.public_send(field)).to be_nil
             end
           end
@@ -294,7 +294,7 @@
         end
 
         it 'parses the cached value' do
-          result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+          result = cache.fetch(key, as: System::BroadcastMessage) { 'block result' }
 
           expect(result).to eq([broadcast_message])
         end