Fixed #32195 -- Added system check for invalid view in path() and improved error messages.
diff --git a/django/urls/conf.py b/django/urls/conf.py
index 119e95df411dd..b3937d55122ea 100644
--- a/django/urls/conf.py
+++ b/django/urls/conf.py
@@ -55,6 +55,8 @@ def include(arg, namespace=None):
 
 
 def _path(route, view, kwargs=None, name=None, Pattern=None):
+    from django.views import View
+
     if isinstance(view, (list, tuple)):
         # For include(...) processing.
         pattern = Pattern(route, is_endpoint=False)
@@ -69,6 +71,12 @@ def _path(route, view, kwargs=None, name=None, Pattern=None):
     elif callable(view):
         pattern = Pattern(route, name=name, is_endpoint=True)
         return URLPattern(pattern, view, kwargs, name)
+    elif isinstance(view, View):
+        view_cls_name = view.__class__.__name__
+        raise TypeError(
+            f'view must be a callable, pass {view_cls_name}.as_view(), not '
+            f'{view_cls_name}().'
+        )
     else:
         raise TypeError('view must be a callable or a list/tuple in the case of include().')
 
diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py
index fac77fc4bc402..674fd0c58ed9e 100644
--- a/django/urls/resolvers.py
+++ b/django/urls/resolvers.py
@@ -345,6 +345,7 @@ def __repr__(self):
     def check(self):
         warnings = self._check_pattern_name()
         warnings.extend(self.pattern.check())
+        warnings.extend(self._check_callback())
         return warnings
 
     def _check_pattern_name(self):
@@ -361,6 +362,22 @@ def _check_pattern_name(self):
         else:
             return []
 
+    def _check_callback(self):
+        from django.views import View
+
+        view = self.callback
+        if inspect.isclass(view) and issubclass(view, View):
+            return [Error(
+                'Your URL pattern %s has an invalid view, pass %s.as_view() '
+                'instead of %s.' % (
+                    self.pattern.describe(),
+                    view.__name__,
+                    view.__name__,
+                ),
+                id='urls.E009',
+            )]
+        return []
+
     def resolve(self, path):
         match = self.pattern.match(path)
         if match:
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index f304da7e1168c..3147114f8dfab 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -584,6 +584,8 @@ The following checks are performed on your URL configuration:
   take the correct number of arguments (…).
 * **urls.E008**: The custom ``handlerXXX`` view ``'path.to.view'`` could not be
   imported.
+* **urls.E009**: Your URL pattern ``<pattern>`` has an invalid view, pass
+  ``<view>.as_view()`` instead of ``<view>``.
 
 ``contrib`` app checks
 ======================
diff --git a/tests/check_framework/test_urls.py b/tests/check_framework/test_urls.py
index 2ef3d5b07fced..663c7a299ca48 100644
--- a/tests/check_framework/test_urls.py
+++ b/tests/check_framework/test_urls.py
@@ -134,6 +134,16 @@ def test_check_unique_namespaces(self):
         result = check_url_namespaces_unique(None)
         self.assertEqual(result, [])
 
+    @override_settings(ROOT_URLCONF='check_framework.urls.cbv_as_view')
+    def test_check_view_not_class(self):
+        self.assertEqual(check_url_config(None), [
+            Error(
+                "Your URL pattern 'missing_as_view' has an invalid view, pass "
+                "EmptyCBV.as_view() instead of EmptyCBV.",
+                id='urls.E009',
+            ),
+        ])
+
 
 class UpdatedToPathTests(SimpleTestCase):
 
diff --git a/tests/check_framework/urls/cbv_as_view.py b/tests/check_framework/urls/cbv_as_view.py
new file mode 100644
index 0000000000000..932a2bfcc97d6
--- /dev/null
+++ b/tests/check_framework/urls/cbv_as_view.py
@@ -0,0 +1,19 @@
+from django.http import HttpResponse
+from django.urls import path
+from django.views import View
+
+
+class EmptyCBV(View):
+    pass
+
+
+class EmptyCallableView:
+    def __call__(self, request, *args, **kwargs):
+        return HttpResponse()
+
+
+urlpatterns = [
+    path('missing_as_view', EmptyCBV),
+    path('has_as_view', EmptyCBV.as_view()),
+    path('callable_class', EmptyCallableView()),
+]
diff --git a/tests/urlpatterns/tests.py b/tests/urlpatterns/tests.py
index 1cd3523ca5055..dca9f630866ab 100644
--- a/tests/urlpatterns/tests.py
+++ b/tests/urlpatterns/tests.py
@@ -5,6 +5,7 @@
 from django.test import SimpleTestCase
 from django.test.utils import override_settings
 from django.urls import NoReverseMatch, Resolver404, path, resolve, reverse
+from django.views import View
 
 from .converters import DynamicConverter
 from .views import empty_view
@@ -141,6 +142,19 @@ def test_invalid_converter(self):
         with self.assertRaisesMessage(ImproperlyConfigured, msg):
             path('foo/<nonexistent:var>/', empty_view)
 
+    def test_invalid_view(self):
+        msg = 'view must be a callable or a list/tuple in the case of include().'
+        with self.assertRaisesMessage(TypeError, msg):
+            path('articles/', 'invalid_view')
+
+    def test_invalid_view_instance(self):
+        class EmptyCBV(View):
+            pass
+
+        msg = 'view must be a callable, pass EmptyCBV.as_view(), not EmptyCBV().'
+        with self.assertRaisesMessage(TypeError, msg):
+            path('foo', EmptyCBV())
+
     def test_whitespace_in_route(self):
         msg = (
             "URL route 'space/<int:num>/extra/<str:%stest>' cannot contain "

Ticket https://code.djangoproject.com/ticket/32195#ticket