The world champion dropped to third in the opening stages, but made two brilliant moves in nine race-winning laps to claim his second win of the season.
Verstappen crossed the line in second, while Bottas, who started from pole, finished a disappointing third. Sergio Perez took the flag in fourth, with Lando Norris fifth for McLaren.
Hamilton missed out on top spot in qualifying to Bottas by just seven thousandths of a second, and the British driver faced an uphill task to win for a 97th time following his poor restart.
The safety car was deployed at the end of the first lap after Kimi Raikkonen ran into the back of Alfa Romeo team-mate Antonio Giovinazzi.
And six laps later, when the debris was cleared, Hamilton was caught napping as Verstappen sailed round the outside of his Mercedes at the opening corner.
Up front, Bottas was unable to race into the distance and the top three spent the formative laps separated by little more than one second.
On lap 11, Hamilton landed his first strike by moving back past Verstappen. He drew alongside the Dutchman at 200mph before sliding up the inside of the Red Bull at the first corner.
Verstappen eyed up Hamilton through turn four, but the world champion planted his Mercedes on the apex and Verstappen was forced to yield.
It wasn’t long before Hamilton was then crawling all over the back of Bottas. Indeed, one wondered whether the Mercedes pit wall would order the Finn to move aside with Verstappen in striking range.
But Hamilton took the decision out of their hands when he moved into the lead will a dazzling pass on lap 20 of 66.
Bottas defended the inside line heading into the first corner. Hamilton followed in the slipstream of his Mercedes team-mate before suddenly darting to the left in the braking zone and passing Bottas around the outside.
Such was Hamilton’s speed, he was suddenly two seconds clear of Bottas inside just four laps, and from there he never looked like losing.
Verstappen, stuck behind Bottas, was unable to make any impression on his title rival and he was the first of the leaders to stop for fresh rubber on lap 35.
Bottas came in a lap later and emerged with a comfortable advantage over the Red Bull car, but the Finn was slow in getting his tyres up to speed and Verstappen pounced within five corners to take second place.
It capped another disappointing afternoon for Bottas, already 37 points adrift of Hamilton after just three rounds.
Perez, out of sync with the leaders by virtue of a longer opening stint, provided Hamilton with some danger. The Mexican led the race for a period but Hamilton sailed past with 15 laps remaining.
Verstappen put on a fresh set of tyres in the closing stages and appeared to claim a bonus point with the fastest lap of the race but his time was delated for exceeding track limits. Hamilton will head to next week’s Spanish Grand Prix with an eight-point lead over Verstappen in his quest for an eighth world title.
Charles Leclerc finished sixth for Ferrari, one spot ahead of Alpine’s Esteban Ocon. Double world champion Fernando Alonso finished eighth following a late move on Ferrari’s Carlos Sainz.
Verstappen’s fastest lap was deleted for exceeding track limits, meaning Hamilton is eight points clear in the standings.
“That was such a tough race, physically and mentally and just keeping it together,” said Hamilton. “It was windy, so it was easy to put a foot wrong.
“I lost out at the restart which was not good. I was not happy and I had to position myself the best I could. I then had to make a move on Valtteri before the tyres were destroyed, and I just made it stick at turn one.”
Verstappen said: “At the end we just lacked a bit of pace and Lewis got by.
“Once we settled in second you could see we were lacking in pace compared to them. We were not on top of it here, but we will see what we can do in Barcelona.”