You didn't tell your database to use eventually existing indexes.
Your first explain has kind of join constraint "messages.username = 'johndoe' AND users.username = 'johndoe'". Such similar thing is missing in second explain.
I would also suggest always to join tables in such a way:
...
FROM messages JOIN users ON messages.username = user.username
...
But be aware that username (e.g. 'johndoe') usually is not unique. If so, the join constraint of your first explain and others would generate wrong results when executing the queries.
So it is mostly a good idea defining convenient primary keys and foreign keys for every table what explicitly prevents from generating wrong joins. Additional profit: your database can easily find existing indexes (they are put automatically on every primary key column by your database, thus it knows them) to speed up table scan when joining tables. If you hide such keys from the database and the database determines that large joins cannot be computed without indexes, database is forced to generate temporary indexes every time join is executed, what is sheer waste.
-- tesu