diff --git a/rbtools/utils/encoding.py b/rbtools/utils/encoding.py
index 68679c378affde7fba85bcd8bd59726c5217f600..197bfc91f3843de2dc4279dfbe14f9620a176ad2 100644
--- a/rbtools/utils/encoding.py
+++ b/rbtools/utils/encoding.py
@@ -1,17 +1,61 @@
 """Utilities for managing string types and encoding."""
 
-from typing import Union
+from __future__ import annotations
 
+from typing import Any, Literal, Union, overload
 
+
+@overload
 def force_bytes(
     string: Union[bytes, str],
+    encoding: str = ...,
+    *,
+    strings_only: Literal[True] = ...,
+) -> bytes:
+    ...
+
+
+@overload
+def force_bytes(
+    string: Any,
+    encoding: str = ...,
+    *,
+    strings_only: Literal[False],
+) -> bytes:
+    ...
+
+
+def force_bytes(
+    string: Any,
     encoding: str = 'utf-8',
+    *,
+    strings_only: bool = True,
 ) -> bytes:
-    """Force a given string to be a bytes type.
+    """Force a given string to be a byte string type.
+
+    Version Changed:
+        5.0:
+        Added the ``strings_only`` parameter.
 
     Args:
-        string (bytes or str):
-            The string to enforce.
+        string (bytes or str or object):
+            The string to enforce, or an object that can cast to a string
+            type.
+
+        encoding (str, optional):
+            The optional encoding, if encoding Unicode strings.
+
+        strings_only (bool, optional):
+            Whether to only transform byte/Unicode strings.
+
+            If ``True`` (the default), any other type will result in an error.
+
+            If ``False``, the object will be cast to a string using
+            the object's :py:meth:`~object.__bytes__` or
+            :py:meth:`~object.__str__` method.
+
+            Version Added:
+                5.0
 
     Returns:
         bytes:
@@ -25,19 +69,67 @@ def force_bytes(
         return string
     elif isinstance(string, str):
         return string.encode(encoding)
-    else:
-        raise ValueError('Provided string was neither bytes nor unicode')
+    elif not strings_only:
+        if hasattr(string, '__bytes__'):
+            return bytes(string)
+        else:
+            return str(string).encode(encoding)
 
+    raise ValueError(
+        'The provided value could not be cast to a byte string: %r'
+        % (string,))
 
+
+@overload
 def force_unicode(
     string: Union[bytes, str],
+    encoding: str = ...,
+    *,
+    strings_only: Literal[True] = ...,
+) -> str:
+    ...
+
+
+@overload
+def force_unicode(
+    string: Any,
+    encoding: str = ...,
+    *,
+    strings_only: Literal[False],
+) -> str:
+    ...
+
+
+def force_unicode(
+    string: Any,
     encoding: str = 'utf-8',
+    *,
+    strings_only: bool = True,
 ) -> str:
     """Force a given string to be a Unicode string type.
 
+    Version Changed:
+        5.0:
+        Added the ``strings_only`` parameter.
+
     Args:
-        string (bytes or str):
-            The string to enforce.
+        string (bytes or str or object):
+            The string to enforce, or an object that can cast to a string
+            type.
+
+        encoding (str, optional):
+            The optional encoding, if decoding byte strings.
+
+        strings_only (bool, optional):
+            Whether to only transform byte/Unicode strings.
+
+            If ``True`` (the default), any other type will result in an error.
+
+            If ``False``, the object will be cast to a string using
+            the object's :py:meth:`~object.__str__` method.
+
+            Version Added:
+                5.0
 
     Returns:
         str:
@@ -51,5 +143,9 @@ def force_unicode(
         return string
     elif isinstance(string, bytes):
         return string.decode(encoding)
+    elif not strings_only and hasattr(string, '__str__'):
+        return str(string)
     else:
-        raise ValueError('Provided string was neither bytes nor unicode')
+        raise ValueError(
+            'The provided value could not be cast to a Unicode string: %r'
+            % (string,))
diff --git a/rbtools/utils/tests/test_encoding.py b/rbtools/utils/tests/test_encoding.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3c9a608b693d5dee1ccebfc7d8c225ac9b63469
--- /dev/null
+++ b/rbtools/utils/tests/test_encoding.py
@@ -0,0 +1,119 @@
+"""Unit tests for rbtools.utils.encoding.
+
+Version Added:
+    5.0
+"""
+
+from __future__ import annotations
+
+from rbtools.testing import TestCase
+from rbtools.utils.encoding import force_bytes, force_unicode
+
+
+class MyBytesObject:
+    def __bytes__(self) -> bytes:
+        return b'a\x1b\x2c\x99'
+
+
+class MyStrObject:
+    def __str__(self) -> str:
+        return 'abc 🙃 def'
+
+
+class ForceBytesTests(TestCase):
+    """Unit tests for force_bytes.
+
+    Version Added:
+        5.0
+    """
+
+    def test_with_bytes(self) -> None:
+        """Testing force_bytes with bytes"""
+        self.assertEqual(force_bytes(b'a\x1b\x2c\x99'),
+                         b'a\x1b\x2c\x99')
+
+    def test_with_str(self) -> None:
+        """Testing force_bytes with Unicode string"""
+        self.assertEqual(force_bytes('abc 🙃 def'),
+                         b'abc \xf0\x9f\x99\x83 def')
+
+    def test_with_cast_to_bytes(self) -> None:
+        """Testing force_bytes with object with __bytes__ method and default
+        behavior
+        """
+        obj = MyBytesObject()
+
+        message = (
+            'The provided value could not be cast to a byte string: %r'
+            % obj
+        )
+
+        with self.assertRaisesMessage(ValueError, message):
+            force_bytes(obj)  # type: ignore
+
+    def test_with_cast_to_bytes_and_strings_only_false(self) -> None:
+        """Testing force_bytes with object with __bytes__ method and
+        strings_only=False
+        """
+        self.assertEqual(force_bytes(MyBytesObject(), strings_only=False),
+                         b'a\x1b\x2c\x99')
+
+    def test_with_cast_to_str(self) -> None:
+        """Testing force_bytes with object with __str__ method and default
+        behavior
+        """
+        obj = MyStrObject()
+
+        message = (
+            'The provided value could not be cast to a byte string: %r'
+            % obj
+        )
+
+        with self.assertRaisesMessage(ValueError, message):
+            force_bytes(obj)  # type: ignore
+
+    def test_with_cast_to_str_and_strings_only_false(self) -> None:
+        """Testing force_bytes with object with __str__ method and
+        strings_only=False
+        """
+        self.assertEqual(force_bytes(MyStrObject(), strings_only=False),
+                         b'abc \xf0\x9f\x99\x83 def')
+
+
+class ForceUnicodeTests(TestCase):
+    """Unit tests for force_unicode.
+
+    Version Added:
+        5.0
+    """
+
+    def test_with_str(self) -> None:
+        """Testing force_unicode with Unicode string"""
+        self.assertEqual(force_unicode('abc 🙃 def'),
+                         'abc 🙃 def')
+
+    def test_with_bytes(self) -> None:
+        """Testing force_unicode with bytes"""
+        self.assertEqual(force_unicode(b'abc \xf0\x9f\x99\x83 def'),
+                         'abc 🙃 def')
+
+    def test_with_cast_to_str(self) -> None:
+        """Testing force_unicode with object with __str__ method and default
+        behavior
+        """
+        obj = MyStrObject()
+
+        message = (
+            'The provided value could not be cast to a Unicode string: %r'
+            % obj
+        )
+
+        with self.assertRaisesMessage(ValueError, message):
+            force_unicode(obj)  # type: ignore
+
+    def test_with_cast_to_str_and_strings_only_false(self) -> None:
+        """Testing force_unicode with object with __str__ method and
+        strings_only=False
+        """
+        self.assertEqual(force_unicode(MyStrObject(), strings_only=False),
+                         'abc 🙃 def')
