diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2016 Daniel Mendler
+Copyright (c) 2016-2017 Daniel Mendler
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/intro.cabal b/intro.cabal
--- a/intro.cabal
+++ b/intro.cabal
@@ -3,7 +3,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           intro
-version:        0.1.0.10
+version:        0.2.0.0
 synopsis:       "Fixed Prelude" - Mostly total and safe, provides Text and Monad transformers
 description:    Intro is a modern Prelude which provides safe alternatives
                 for most of the partial functions and follows other
@@ -22,7 +22,7 @@
 bug-reports:    https://github.com/minad/intro/issues
 author:         Daniel Mendler <mail@daniel-mendler.de>
 maintainer:     Daniel Mendler <mail@daniel-mendler.de>
-copyright:      2016 Daniel Mendler
+copyright:      2016-2017 Daniel Mendler
 license:        MIT
 license-file:   LICENSE
 tested-with:    GHC == 7.10.3, GHC == 8.0.1
@@ -52,7 +52,6 @@
     , hashable             >= 1.2.5   && < 1.3
     , mtl                  >= 2.2     && < 2.3
     , safe                 >= 0.3.11  && < 0.4
-    , string-conversions   >= 0.4     && < 0.5
     , tagged               >= 0.8     && < 0.9
     , text                 >= 0.7     && < 1.3
     , transformers         >= 0.4     && < 0.6
@@ -64,13 +63,14 @@
   exposed-modules:
       Intro
   other-modules:
+      Intro.ConvertString
       Intro.Trustworthy
       Paths_intro
   default-language: Haskell2010
 
-test-suite compat
+test-suite test
   type: exitcode-stdio-1.0
-  main-is: compat.hs
+  main-is: test.hs
   hs-source-dirs:
       test
   ghc-options: -Wall
@@ -86,7 +86,6 @@
     , hashable             >= 1.2.5   && < 1.3
     , mtl                  >= 2.2     && < 2.3
     , safe                 >= 0.3.11  && < 0.4
-    , string-conversions   >= 0.4     && < 0.5
     , tagged               >= 0.8     && < 0.9
     , text                 >= 0.7     && < 1.3
     , transformers         >= 0.4     && < 0.6
@@ -94,6 +93,8 @@
     , writer-cps-mtl       >= 0.1.1.2 && < 0.2
     , intro
     , lens
+    , QuickCheck
+    , quickcheck-instances
   if impl(ghc < 8.0)
     build-depends:
         semigroups >= 0.9 && < 1
diff --git a/src/Intro.hs b/src/Intro.hs
--- a/src/Intro.hs
+++ b/src/Intro.hs
@@ -9,7 +9,7 @@
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Intro
--- Copyright   :  (c) Daniel Mendler 2016
+-- Copyright   :  (c) Daniel Mendler 2016-2017
 -- License     :  MIT
 --
 -- Maintainer  :  mail@daniel-mendler.de
@@ -34,11 +34,11 @@
 -- * Avoid writing custom functions
 -- * Export everything explicitly to provide a stable interface and for good documentation
 -- * Export only total functions or provide safe alternatives (Very few exceptions like div etc.)
--- * Prefer Text over String, provide ConvertibleStrings
+-- * Prefer Text over String, provide 'ConvertString' and 'EncodeString'
 -- * Provide Monad transformers
 -- * Provide container types
 -- * Prefer generic functions
--- * Debugging functions, like 'Intro.Trustworthy.trace' and 'undefined' are available but produce compile time warnings
+-- * Debugging functions, like 'trace' and 'undefined' are available but produce compile time warnings
 -- * Don't provide error, only panic instead
 -- * Compatibility with Control.Lens
 --
@@ -70,7 +70,7 @@
 -- * 'gcd' and 'lcm' are not commonly used.
 -- * 'error' and 'errorWithoutStackTrace' are not provided. Use 'panic' instead.
 -- * 'ioError' and 'userError' are not provided. Import modules for exception handling separately if needed.
--- * Some 'Read' and 'Show' class functions are not provided. Don't write these instances yourself.
+-- * Some 'Text.Read.Read' and 'Show' class functions are not provided. Don't write these instances yourself.
 --
 -- Additional types and functions:
 --
@@ -187,7 +187,6 @@
   , Data.List.zip3
   , Data.List.zipWith
   , Data.List.zipWith3
-  -- , Data.List.cycle -- partial
   , Safe.headDef
   , Safe.headMay -- prefer pattern match
   , Safe.initDef
@@ -245,7 +244,9 @@
 
   -- ** Conversion
   , Data.String.IsString(fromString)
-  , Data.String.Conversions.ConvertibleStrings(convertString)
+  , Intro.ConvertString.ConvertString(convertString)
+  , Intro.ConvertString.EncodeString(encodeString, decodeString, decodeStringLenient)
+  , Lenient(..)
 
   -- * Container types
 
@@ -668,11 +669,10 @@
 import Data.Maybe (Maybe, fromMaybe)
 import Data.Semigroup (Semigroup((<>)))
 import Data.String (IsString(fromString), String)
-import Data.String.Conversions (ConvertibleStrings(convertString))
 import Data.Text (Text)
-import Intro.Trustworthy (HasCallStack, IsList(Item, toList, fromList))
+import Intro.ConvertString
+import Intro.Trustworthy
 import System.IO (FilePath)
-import Text.Read (Read)
 import Text.Show (Show)
 import qualified Control.Applicative
 import qualified Control.Category
@@ -729,7 +729,6 @@
 import qualified Data.Void
 import qualified Data.Word
 import qualified GHC.Generics
-import qualified Intro.Trustworthy
 import qualified Numeric.Natural
 import qualified Prelude
 import qualified Safe
@@ -769,9 +768,9 @@
 map = fmap
 {-# INLINE map #-}
 
--- | Convert a value to a readable string type supported by 'ConvertibleStrings' using the 'Show' instance.
-show :: (Show a, IsString s) => a -> s
-show = fromString . showS
+-- | Convert a value to a readable string type supported by 'ConvertString' using the 'Show' instance.
+show :: (Show a, ConvertString String s) => a -> s
+show = convertString . showS
 {-# INLINE show #-}
 
 -- | Convert a value to a readable 'Text' using the 'Show' instance.
@@ -786,7 +785,7 @@
 
 -- | Parse a string type using the 'Text.Read.Read' instance.
 -- Succeeds if there is exactly one valid result.
-readMaybe :: (Text.Read.Read b, ConvertibleStrings a String) => a -> Maybe b
+readMaybe :: (Text.Read.Read b, ConvertString a String) => a -> Maybe b
 readMaybe = Text.Read.readMaybe . convertString
 {-# INLINE readMaybe #-}
 
@@ -870,10 +869,12 @@
 {-# INLINE appendFile #-}
 
 -- | Read an entire file strictly into a 'Text' using UTF-8 encoding.
+-- The decoding is done using 'decodeStringLenient'. Invalid characters are replaced
+-- by the Unicode replacement character '\FFFD'.
 --
 -- __Note__: This function is lifted to the 'MonadIO' class.
 readFileUtf8 :: MonadIO m => FilePath -> m Text
-readFileUtf8 = map convertString . readFile
+readFileUtf8 = map decodeStringLenient . readFile
 {-# INLINE readFileUtf8 #-}
 
 -- | Write a 'Text' to a file using UTF-8 encoding.
diff --git a/src/Intro/ConvertString.hs b/src/Intro/ConvertString.hs
new file mode 100644
--- /dev/null
+++ b/src/Intro/ConvertString.hs
@@ -0,0 +1,148 @@
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Intro.ConvertString
+-- Copyright   :  (c) Daniel Mendler 2017
+-- License     :  MIT
+--
+-- Maintainer  :  mail@daniel-mendler.de
+-- Stability   :  experimental
+-- Portability :  portable
+--
+-- String conversion and decoding
+--
+-----------------------------------------------------------------------------
+
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE Safe #-}
+{-# LANGUAGE DefaultSignatures #-}
+
+module Intro.ConvertString (
+    ConvertString(..)
+  , EncodeString(..)
+  , Lenient(..)
+) where
+
+import Data.ByteString (ByteString)
+import Data.Either.Extra (eitherToMaybe)
+import Data.Eq (Eq)
+import Data.Foldable (Foldable)
+import Data.Function (id, (.))
+import Data.Functor (Functor(fmap))
+import Data.Maybe (Maybe)
+import Data.Ord (Ord)
+import Data.String (String)
+import Data.Text (Text)
+import Data.Text.Encoding.Error (lenientDecode)
+import Data.Traversable (Traversable)
+import Data.Word (Word8)
+import Text.Show (Show)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TLE
+
+-- | Conversion of strings to other string types
+--
+-- @
+-- ('convertString' :: b -> a)           . ('convertString' :: a -> b) ≡ ('id'      :: a -> a)
+-- ('convertString' :: b -> 'Maybe' a)   . ('convertString' :: a -> b) ≡ ('Just'    :: a -> 'Maybe' a)
+-- ('convertString' :: b -> 'Lenient' a) . ('convertString' :: a -> b) ≡ ('Lenient' :: a -> 'Lenient' a)
+-- @
+class ConvertString a b where
+  -- | Convert a string to another string type
+  convertString :: a -> b
+
+-- | Encode and decode strings as a byte sequence
+--
+-- @
+-- 'decodeString'        . 'encodeString' ≡ 'Just'
+-- 'decodeStringLenient' . 'encodeString' ≡ 'id'
+-- @
+class (ConvertString a b, ConvertString b (Maybe a), ConvertString b (Lenient a)) => EncodeString a b where
+  -- | Encode a string as a byte sequence
+  encodeString :: a -> b
+  encodeString = convertString
+  {-# INLINE encodeString #-}
+
+  -- | Lenient decoding of byte sequence
+  --
+  -- Lenient means that invalid characters are replaced
+  -- by the Unicode replacement character '\FFFD'.
+  decodeStringLenient :: b -> a
+  decodeStringLenient = getLenient . convertString
+  {-# INLINE decodeStringLenient #-}
+
+  -- | Decode byte sequence
+  --
+  -- If the decoding fails, return Nothing.
+  decodeString :: b -> Maybe a
+  decodeString = convertString
+  {-# INLINE decodeString #-}
+
+-- | Newtype wrapper for a string which was decoded leniently.
+newtype Lenient a = Lenient { getLenient :: a }
+  deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
+
+instance ConvertString BL.ByteString (Lenient String)    where {-# INLINE convertString #-}; convertString = Lenient . TL.unpack . TLE.decodeUtf8With lenientDecode
+instance ConvertString BL.ByteString (Lenient TL.Text)   where {-# INLINE convertString #-}; convertString = Lenient . TLE.decodeUtf8With lenientDecode
+instance ConvertString BL.ByteString (Lenient Text)      where {-# INLINE convertString #-}; convertString = Lenient . TE.decodeUtf8With lenientDecode . BL.toStrict
+instance ConvertString BL.ByteString (Maybe   String)    where {-# INLINE convertString #-}; convertString = fmap TL.unpack . eitherToMaybe . TLE.decodeUtf8'
+instance ConvertString BL.ByteString (Maybe   TL.Text)   where {-# INLINE convertString #-}; convertString = eitherToMaybe . TLE.decodeUtf8'
+instance ConvertString BL.ByteString (Maybe   Text)      where {-# INLINE convertString #-}; convertString = eitherToMaybe . TE.decodeUtf8' . BL.toStrict
+instance ConvertString BL.ByteString BL.ByteString       where {-# INLINE convertString #-}; convertString = id
+instance ConvertString BL.ByteString ByteString          where {-# INLINE convertString #-}; convertString = BL.toStrict
+instance ConvertString BL.ByteString [Word8]             where {-# INLINE convertString #-}; convertString = BL.unpack
+instance ConvertString ByteString    (Lenient String)    where {-# INLINE convertString #-}; convertString = Lenient . T.unpack . TE.decodeUtf8With lenientDecode
+instance ConvertString ByteString    (Lenient TL.Text)   where {-# INLINE convertString #-}; convertString = Lenient . TLE.decodeUtf8With lenientDecode . BL.fromStrict
+instance ConvertString ByteString    (Lenient Text)      where {-# INLINE convertString #-}; convertString = Lenient . TE.decodeUtf8With lenientDecode
+instance ConvertString ByteString    (Maybe   String)    where {-# INLINE convertString #-}; convertString = fmap T.unpack . eitherToMaybe . TE.decodeUtf8'
+instance ConvertString ByteString    (Maybe   TL.Text)   where {-# INLINE convertString #-}; convertString = eitherToMaybe . TLE.decodeUtf8' . BL.fromStrict
+instance ConvertString ByteString    (Maybe   Text)      where {-# INLINE convertString #-}; convertString = eitherToMaybe . TE.decodeUtf8'
+instance ConvertString ByteString    BL.ByteString       where {-# INLINE convertString #-}; convertString = BL.fromStrict
+instance ConvertString ByteString    ByteString          where {-# INLINE convertString #-}; convertString = id
+instance ConvertString ByteString    [Word8]             where {-# INLINE convertString #-}; convertString = B.unpack
+instance ConvertString String        BL.ByteString       where {-# INLINE convertString #-}; convertString = TLE.encodeUtf8 . TL.pack
+instance ConvertString String        ByteString          where {-# INLINE convertString #-}; convertString = TE.encodeUtf8 . T.pack
+instance ConvertString String        String              where {-# INLINE convertString #-}; convertString = id
+instance ConvertString String        TL.Text             where {-# INLINE convertString #-}; convertString = TL.pack
+instance ConvertString String        Text                where {-# INLINE convertString #-}; convertString = T.pack
+instance ConvertString String        [Word8]             where {-# INLINE convertString #-}; convertString = BL.unpack . TLE.encodeUtf8 . TL.pack
+instance ConvertString TL.Text       BL.ByteString       where {-# INLINE convertString #-}; convertString = TLE.encodeUtf8
+instance ConvertString TL.Text       ByteString          where {-# INLINE convertString #-}; convertString = BL.toStrict . TLE.encodeUtf8
+instance ConvertString TL.Text       String              where {-# INLINE convertString #-}; convertString = TL.unpack
+instance ConvertString TL.Text       TL.Text             where {-# INLINE convertString #-}; convertString = id
+instance ConvertString TL.Text       Text                where {-# INLINE convertString #-}; convertString = TL.toStrict
+instance ConvertString TL.Text       [Word8]             where {-# INLINE convertString #-}; convertString = BL.unpack . TLE.encodeUtf8
+instance ConvertString Text          BL.ByteString       where {-# INLINE convertString #-}; convertString = BL.fromStrict . TE.encodeUtf8
+instance ConvertString Text          ByteString          where {-# INLINE convertString #-}; convertString = TE.encodeUtf8
+instance ConvertString Text          String              where {-# INLINE convertString #-}; convertString = T.unpack
+instance ConvertString Text          TL.Text             where {-# INLINE convertString #-}; convertString = TL.fromStrict
+instance ConvertString Text          Text                where {-# INLINE convertString #-}; convertString = id
+instance ConvertString Text          [Word8]             where {-# INLINE convertString #-}; convertString = BL.unpack . BL.fromStrict . TE.encodeUtf8
+instance ConvertString [Word8]       (Lenient String)    where {-# INLINE convertString #-}; convertString = Lenient . TL.unpack . TLE.decodeUtf8With lenientDecode . BL.pack
+instance ConvertString [Word8]       (Lenient TL.Text)   where {-# INLINE convertString #-}; convertString = Lenient . TLE.decodeUtf8With lenientDecode . BL.pack
+instance ConvertString [Word8]       (Lenient Text)      where {-# INLINE convertString #-}; convertString = Lenient . TE.decodeUtf8With lenientDecode . B.pack
+instance ConvertString [Word8]       (Maybe String)      where {-# INLINE convertString #-}; convertString = fmap TL.unpack . eitherToMaybe . TLE.decodeUtf8' . BL.pack
+instance ConvertString [Word8]       (Maybe TL.Text)     where {-# INLINE convertString #-}; convertString = eitherToMaybe . TLE.decodeUtf8' . BL.pack
+instance ConvertString [Word8]       (Maybe Text)        where {-# INLINE convertString #-}; convertString = eitherToMaybe . TE.decodeUtf8' . B.pack
+instance ConvertString [Word8]       BL.ByteString       where {-# INLINE convertString #-}; convertString = BL.pack
+instance ConvertString [Word8]       ByteString          where {-# INLINE convertString #-}; convertString = B.pack
+instance ConvertString [Word8]       [Word8]             where {-# INLINE convertString #-}; convertString = id
+
+instance EncodeString  String        BL.ByteString
+instance EncodeString  String        ByteString
+instance EncodeString  String        [Word8]
+instance EncodeString  TL.Text       BL.ByteString
+instance EncodeString  TL.Text       ByteString
+instance EncodeString  TL.Text       [Word8]
+instance EncodeString  Text          BL.ByteString
+instance EncodeString  Text          ByteString
+instance EncodeString  Text          [Word8]
diff --git a/src/Intro/Trustworthy.hs b/src/Intro/Trustworthy.hs
--- a/src/Intro/Trustworthy.hs
+++ b/src/Intro/Trustworthy.hs
@@ -7,7 +7,7 @@
 -----------------------------------------------------------------------------
 -- |
 -- Module      :  Intro.Trustworthy
--- Copyright   :  (c) Daniel Mendler 2016
+-- Copyright   :  (c) Daniel Mendler 2016-2017
 -- License     :  MIT
 --
 -- Maintainer  :  mail@daniel-mendler.de
diff --git a/test/compat.hs b/test/compat.hs
deleted file mode 100644
--- a/test/compat.hs
+++ /dev/null
@@ -1,9 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE OverloadedStrings #-}
-module Main where
-
-import BaseCompat
-import LensCompat
-
-main :: IO ()
-main = pure ()
diff --git a/test/test.hs b/test/test.hs
new file mode 100644
--- /dev/null
+++ b/test/test.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE ExplicitForAll #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module Main where
+
+import Intro
+import BaseCompat ()
+import LensCompat ()
+import Test.QuickCheck
+import Test.QuickCheck.Instances ()
+
+main :: IO ()
+main = do
+  encode (Proxy :: Proxy LText)       (Proxy :: Proxy ByteString)
+  encode (Proxy :: Proxy LText)       (Proxy :: Proxy LByteString)
+  encode (Proxy :: Proxy LText)       (Proxy :: Proxy [Word8])
+  encode (Proxy :: Proxy String)      (Proxy :: Proxy ByteString)
+  encode (Proxy :: Proxy String)      (Proxy :: Proxy LByteString)
+  encode (Proxy :: Proxy String)      (Proxy :: Proxy [Word8])
+  encode (Proxy :: Proxy Text)        (Proxy :: Proxy ByteString)
+  encode (Proxy :: Proxy Text)        (Proxy :: Proxy LByteString)
+  encode (Proxy :: Proxy Text)        (Proxy :: Proxy [Word8])
+  iso    (Proxy :: Proxy ByteString)  (Proxy :: Proxy ByteString)
+  iso    (Proxy :: Proxy ByteString)  (Proxy :: Proxy LByteString)
+  iso    (Proxy :: Proxy ByteString)  (Proxy :: Proxy [Word8])
+  iso    (Proxy :: Proxy LByteString) (Proxy :: Proxy ByteString)
+  iso    (Proxy :: Proxy LByteString) (Proxy :: Proxy LByteString)
+  iso    (Proxy :: Proxy LByteString) (Proxy :: Proxy [Word8])
+  iso    (Proxy :: Proxy LText)       (Proxy :: Proxy LText)
+  iso    (Proxy :: Proxy LText)       (Proxy :: Proxy String)
+  iso    (Proxy :: Proxy LText)       (Proxy :: Proxy Text)
+  iso    (Proxy :: Proxy String)      (Proxy :: Proxy LText)
+  iso    (Proxy :: Proxy String)      (Proxy :: Proxy String)
+  iso    (Proxy :: Proxy String)      (Proxy :: Proxy Text)
+  iso    (Proxy :: Proxy Text)        (Proxy :: Proxy LText)
+  iso    (Proxy :: Proxy Text)        (Proxy :: Proxy String)
+  iso    (Proxy :: Proxy Text)        (Proxy :: Proxy Text)
+  iso    (Proxy :: Proxy [Word8])     (Proxy :: Proxy ByteString)
+  iso    (Proxy :: Proxy [Word8])     (Proxy :: Proxy LByteString)
+  iso    (Proxy :: Proxy [Word8])     (Proxy :: Proxy [Word8])
+
+iso :: forall a b proxy. (Eq a, Show a, Arbitrary a, ConvertString a b, ConvertString b a) => proxy a -> proxy b -> IO ()
+iso _ _ = quickCheck $ \(a :: a) -> convertString (convertString a :: b) == a
+
+encode :: forall a b proxy. (Eq a, Show a, Arbitrary a, EncodeString a b) => proxy a -> proxy b -> IO ()
+encode _ _ = do
+  quickCheck $ \(a :: a) -> decodeString (encodeString a :: b) == Just a
+  quickCheck $ \(a :: a) -> decodeStringLenient (encodeString a :: b) == a
