diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
index c74d04c8294a752d7948b5429c8bc62566da922f..0cb9288131d5de4604ea2c0e151795cb8b6a7877 100644
--- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
+++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs
@@ -92,9 +92,33 @@ namespace Grpc.Core.Tests
 
             var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
             Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+            Assert.AreEqual(0, ex.Trailers.Count);
 
             var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
             Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
+            Assert.AreEqual(0, ex.Trailers.Count);
+        }
+
+        [Test]
+        public void UnaryCall_ServerHandlerThrowsRpcExceptionWithTrailers()
+        {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>((request, context) =>
+            {
+                var trailers = new Metadata { {"xyz", "xyz-value"} };
+                throw new RpcException(new Status(StatusCode.Unauthenticated, ""), trailers);
+            });
+
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+            Assert.AreEqual(1, ex.Trailers.Count);
+            Assert.AreEqual("xyz", ex.Trailers[0].Key);
+            Assert.AreEqual("xyz-value", ex.Trailers[0].Value);
+
+            var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
+            Assert.AreEqual(1, ex2.Trailers.Count);
+            Assert.AreEqual("xyz", ex2.Trailers[0].Key);
+            Assert.AreEqual("xyz-value", ex2.Trailers[0].Value);
         }
 
         [Test]
@@ -108,9 +132,34 @@ namespace Grpc.Core.Tests
 
             var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
             Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+            Assert.AreEqual(0, ex.Trailers.Count);
+
+            var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
+            Assert.AreEqual(0, ex2.Trailers.Count);
+        }
+
+        [Test]
+        public void UnaryCall_ServerHandlerSetsStatusAndTrailers()
+        {
+            helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
+            {
+                context.Status = new Status(StatusCode.Unauthenticated, "");
+                context.ResponseTrailers.Add("xyz", "xyz-value");
+                return "";
+            });
+
+            var ex = Assert.Throws<RpcException>(() => Calls.BlockingUnaryCall(helper.CreateUnaryCall(), "abc"));
+            Assert.AreEqual(StatusCode.Unauthenticated, ex.Status.StatusCode);
+            Assert.AreEqual(1, ex.Trailers.Count);
+            Assert.AreEqual("xyz", ex.Trailers[0].Key);
+            Assert.AreEqual("xyz-value", ex.Trailers[0].Value);
 
             var ex2 = Assert.ThrowsAsync<RpcException>(async () => await Calls.AsyncUnaryCall(helper.CreateUnaryCall(), "abc"));
             Assert.AreEqual(StatusCode.Unauthenticated, ex2.Status.StatusCode);
+            Assert.AreEqual(1, ex2.Trailers.Count);
+            Assert.AreEqual("xyz", ex2.Trailers[0].Key);
+            Assert.AreEqual("xyz-value", ex2.Trailers[0].Value);
         }
 
         [Test]
@@ -148,7 +197,7 @@ namespace Grpc.Core.Tests
             CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, await call.ResponseStream.ToListAsync());
 
             Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
-            Assert.IsNotNull("xyz", call.GetTrailers()[0].Key);
+            Assert.AreEqual("xyz", call.GetTrailers()[0].Key);
         }
 
         [Test]
@@ -182,6 +231,27 @@ namespace Grpc.Core.Tests
             Assert.AreEqual(StatusCode.InvalidArgument, ex2.Status.StatusCode);
         }
 
+        [Test]
+        public async Task ServerStreamingCall_TrailersFromMultipleSourcesGetConcatenated()
+        {
+            helper.ServerStreamingHandler = new ServerStreamingServerMethod<string, string>(async (request, responseStream, context) =>
+            {
+                context.ResponseTrailers.Add("xyz", "xyz-value");
+                throw new RpcException(new Status(StatusCode.InvalidArgument, ""), new Metadata { {"abc", "abc-value"} });
+            });
+
+            var call = Calls.AsyncServerStreamingCall(helper.CreateServerStreamingCall(), "");
+
+            var ex = Assert.ThrowsAsync<RpcException>(async () => await call.ResponseStream.MoveNext());
+            Assert.AreEqual(StatusCode.InvalidArgument, ex.Status.StatusCode);
+            Assert.AreEqual(2, call.GetTrailers().Count);
+            Assert.AreEqual(2, ex.Trailers.Count);
+            Assert.AreEqual("xyz", ex.Trailers[0].Key);
+            Assert.AreEqual("xyz-value", ex.Trailers[0].Value);
+            Assert.AreEqual("abc", ex.Trailers[1].Key);
+            Assert.AreEqual("abc-value", ex.Trailers[1].Value);
+        }
+
         [Test]
         public async Task DuplexStreamingCall()
         {
@@ -199,7 +269,7 @@ namespace Grpc.Core.Tests
             CollectionAssert.AreEqual(new string[] { "A", "B", "C" }, await call.ResponseStream.ToListAsync());
 
             Assert.AreEqual(StatusCode.OK, call.GetStatus().StatusCode);
-            Assert.IsNotNull("xyz-value", call.GetTrailers()[0].Value);
+            Assert.AreEqual("xyz-value", call.GetTrailers()[0].Value);
         }
 
         [Test]
diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
index 6e6ca7cd53cdab6f96d7b5cf4f9df20228b7c96f..17109de587ba2eff709cfc8e3a7b40ecdd9923fc 100644
--- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs
+++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs
@@ -329,7 +329,7 @@ namespace Grpc.Core.Internal
 
         protected override Exception GetRpcExceptionClientOnly()
         {
-            return new RpcException(finishedStatus.Value.Status);
+            return new RpcException(finishedStatus.Value.Status, finishedStatus.Value.Trailers);
         }
 
         protected override Task CheckSendAllowedOrEarlyResult()
@@ -348,7 +348,7 @@ namespace Grpc.Core.Internal
                 // Writing after the call has finished is not a programming error because server can close
                 // the call anytime, so don't throw directly, but let the write task finish with an error.
                 var tcs = new TaskCompletionSource<object>();
-                tcs.SetException(new RpcException(finishedStatus.Value.Status));
+                tcs.SetException(new RpcException(finishedStatus.Value.Status, finishedStatus.Value.Trailers));
                 return tcs.Task;
             }
 
@@ -468,7 +468,7 @@ namespace Grpc.Core.Internal
             var status = receivedStatus.Status;
             if (status.StatusCode != StatusCode.OK)
             {
-                unaryResponseTcs.SetException(new RpcException(status));
+                unaryResponseTcs.SetException(new RpcException(status, receivedStatus.Trailers));
                 return;
             }
 
@@ -506,7 +506,7 @@ namespace Grpc.Core.Internal
             var status = receivedStatus.Status;
             if (status.StatusCode != StatusCode.OK)
             {
-                streamingResponseCallFinishedTcs.SetException(new RpcException(status));
+                streamingResponseCallFinishedTcs.SetException(new RpcException(status, receivedStatus.Trailers));
                 return;
             }
 
diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
index 36702a3fab43d576d669b7aa1b110c2079d9b3d1..6019f8e79346414ddb74bffd9411a82f8e75deca 100644
--- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
+++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs
@@ -76,7 +76,7 @@ namespace Grpc.Core.Internal
                 {
                     Logger.Warning(e, "Exception occured in handler.");
                 }
-                status = HandlerUtils.StatusFromException(e);
+                status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers);
             }
             try
             {
@@ -133,7 +133,7 @@ namespace Grpc.Core.Internal
                 {
                     Logger.Warning(e, "Exception occured in handler.");
                 }
-                status = HandlerUtils.StatusFromException(e);
+                status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers);
             }
 
             try
@@ -191,7 +191,7 @@ namespace Grpc.Core.Internal
                 {
                     Logger.Warning(e, "Exception occured in handler.");
                 }
-                status = HandlerUtils.StatusFromException(e);
+                status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers);
             }
 
             try
@@ -247,7 +247,7 @@ namespace Grpc.Core.Internal
                 {
                     Logger.Warning(e, "Exception occured in handler.");
                 }
-                status = HandlerUtils.StatusFromException(e);
+                status = HandlerUtils.GetStatusFromExceptionAndMergeTrailers(e, context.ResponseTrailers);
             }
             try
             {
@@ -292,11 +292,20 @@ namespace Grpc.Core.Internal
 
     internal static class HandlerUtils
     {
-        public static Status StatusFromException(Exception e)
+        public static Status GetStatusFromExceptionAndMergeTrailers(Exception e, Metadata callContextResponseTrailers)
         {
             var rpcException = e as RpcException;
             if (rpcException != null)
             {
+                // There are two sources of metadata entries on the server-side:
+                // 1. serverCallContext.ResponseTrailers
+                // 2. trailers in RpcException thrown by user code in server side handler.
+                // As metadata allows duplicate keys, the logical thing to do is
+                // to just merge trailers from RpcException into serverCallContext.ResponseTrailers.
+                foreach (var entry in rpcException.Trailers)
+                {
+                    callContextResponseTrailers.Add(entry);
+                }
                 // use the status thrown by handler.
                 return rpcException.Status;
             }
diff --git a/src/csharp/Grpc.Core/RpcException.cs b/src/csharp/Grpc.Core/RpcException.cs
index 01b9e4fb1a14bad8df66d818a97e1fe589ecd67f..d2c912e73af42936139e7924092e8dceb8211a17 100644
--- a/src/csharp/Grpc.Core/RpcException.cs
+++ b/src/csharp/Grpc.Core/RpcException.cs
@@ -17,6 +17,7 @@
 #endregion
 
 using System;
+using Grpc.Core.Utils;
 
 namespace Grpc.Core
 {
@@ -26,6 +27,7 @@ namespace Grpc.Core
     public class RpcException : Exception
     {
         private readonly Status status;
+        private readonly Metadata trailers;
 
         /// <summary>
         /// Creates a new <c>RpcException</c> associated with given status.
@@ -34,6 +36,7 @@ namespace Grpc.Core
         public RpcException(Status status) : base(status.ToString())
         {
             this.status = status;
+            this.trailers = Metadata.Empty;
         }
 
         /// <summary>
@@ -44,6 +47,18 @@ namespace Grpc.Core
         public RpcException(Status status, string message) : base(message)
         {
             this.status = status;
+            this.trailers = Metadata.Empty;
+        }
+
+        /// <summary>
+        /// Creates a new <c>RpcException</c> associated with given status and trailing response metadata.
+        /// </summary>
+        /// <param name="status">Resulting status of a call.</param>
+        /// <param name="trailers">Response trailing metadata.</param> 
+        public RpcException(Status status, Metadata trailers) : base(status.ToString())
+        {
+            this.status = status;
+            this.trailers = GrpcPreconditions.CheckNotNull(trailers);
         }
 
         /// <summary>
@@ -56,5 +71,18 @@ namespace Grpc.Core
                 return status;
             }
         }
+
+        /// <summary>
+        /// Gets the call trailing metadata.
+        /// Trailers only have meaningful content for client-side calls (in which case they represent the trailing metadata sent by the server when closing the call).
+        /// Instances of <c>RpcException</c> thrown by the server-side part of the stack will have trailers always set to empty.
+        /// </summary>
+        public Metadata Trailers
+        {
+            get
+            {
+                return trailers;
+            }
+        }
     }
 }
diff --git a/src/csharp/Grpc.IntegrationTesting/CustomErrorDetailsTest.cs b/src/csharp/Grpc.IntegrationTesting/CustomErrorDetailsTest.cs
index be996f91e03c74aa173469d1ffdfbd34025246cc..374c6fc23f0e8e1d03de461a33c441b12a4d48e7 100644
--- a/src/csharp/Grpc.IntegrationTesting/CustomErrorDetailsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/CustomErrorDetailsTest.cs
@@ -65,7 +65,7 @@ namespace Grpc.IntegrationTesting
         }
 
         [Test]
-        public async Task UnaryCall()
+        public async Task ErrorDetailsFromCallObject()
         {
             var call = client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 });
 
@@ -83,7 +83,24 @@ namespace Grpc.IntegrationTesting
             }
         }
 
-        private DebugInfo GetDebugInfo(Metadata trailers)
+        [Test]
+        public async Task ErrorDetailsFromRpcException()
+        {
+            try
+            {
+                await client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 });
+                Assert.Fail();
+            }
+            catch (RpcException e)
+            {
+                Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
+                var debugInfo = GetDebugInfo(e.Trailers);
+                Assert.AreEqual(debugInfo.Detail, ExceptionDetail);
+                Assert.IsNotEmpty(debugInfo.StackEntries);
+            }
+        }
+
+        private static DebugInfo GetDebugInfo(Metadata trailers)
         {
             var entry = trailers.First((e) => e.Key == DebugInfoTrailerName);
             return DebugInfo.Parser.ParseFrom(entry.ValueBytes);