How to Fix the Erlang “Received Unexpected TCP Data on #Port” Error When Using httpc:request

Recently, I was working on a small Erlang script to fetch the content of a web page using the built-in httpc module. Everything seemed fine at first until Erlang randomly threw this strange warning at me:

Received unexpected tcp data on #Port<0.4432>

At first glance, it looked like a low level TCP issue. But I eventually figured out what was really happening — and in this post, I’ll walk you through it so you don’t spend hours scratching your head like I did!

My Original Code

Here’s the initial function I wrote:

geturl(Url) ->
    {ok, RequestId} = httpc:request(
        get,
        {Url, [{"User-Agent", "Opera/9.80 (Windows NT 6.1; U; ru) Presto/2.8.131 Version/11.10"}]},
        [],
        [{sync, false}]
    ),
    M = receive
            {http, {RequestId, {_HttpOk, _ResponseHeaders, _Body}}} ->
                ok;
        after 20000 ->
                not_ok
        end,
    io:format("httprequest to ~p: ~p~n",[Url, M]).

Sometimes it printed:

httprequest to "http://example.com": ok

But other times, BAM:

Received unexpected tcp data on #Port<0.4432>

What Was Actually Going Wrong?

After digging deeper, I figured out the core issue:

Root CauseExplanation
I used {sync, false}This makes the HTTP request asynchronous
I only consumed one responseBut the TCP connection kept sending more data
My receive block stopped listeningThe remaining messages had nowhere to go
httpc_manager logged it as “unexpected tcp data”Because the port was still active

So essentially… the HTTP process was still alive, but I stopped caring about it too early. Erlang politely warned me — in the loudest possible way.

Two Solid Fixes

Depending on your use case, you can avoid the warning in two ways:

ApproachFixBest When
Synchronous requestUse {sync, true} or simply omit itQuick, one-off HTTP requests
Consume all async messagesKeep listening in a loopLong-running async logic

Improved Version

geturl_sync(Url) ->
    case httpc:request(get, {Url, []}, [], []) of
        {ok, {{_HttpVersion, 200, _ReasonPhrase}, _Headers, Body}} ->
            io:format("Success fetching ~p~n~s~n", [Url, Body]);
        {ok, {{_HttpVersion, StatusCode, Reason}, _Headers, _Body}} ->
            io:format("Request failed with status ~p: ~p~n", [StatusCode, Reason]);
        {error, Reason} ->
            io:format("Request error: ~p~n", [Reason])
    end.

Simple. Clean. No weird TCP ghosts haunting your logs.

Improved Async Version

geturl_async(Url) ->
    {ok, RequestId} = httpc:request(get, {Url, []}, [], [{sync, false}]),
    wait_for_response(RequestId).

wait_for_response(RequestId) ->
    receive
        {http, {RequestId, {ok, _Headers, Body}}} ->
            io:format("Success: ~s~n", [Body]),
            wait_for_more();
        {http, {RequestId, {error, Reason}}} ->
            io:format("Error: ~p~n", [Reason]),
            wait_for_more();
        After ->
            io:format("Unhandled message: ~p~n", [After]),
            wait_for_more()
    after 20000 ->
        io:format("Timeout waiting for response~n")
    end.

wait_for_more() ->
    receive
        {http, Msg} ->
            io:format("Additional message: ~p~n", [Msg]),
            wait_for_more()
    after 1000 ->
        io:format("Done processing async responses~n")
    end.

This version properly flushes all TCP messages, so nothing is left dangling.

Final Thought

Erlang didn’t throw that cryptic TCP warning because something was broken it was actually warning me that I wasn’t finishing what I started. Once I understood that asynchronous httpc calls can leave unread TCP messages behind, the fix became obvious: either go fully synchronous or handle async responses properly.

Related blog posts