Hamilton recovered from losing the lead to Vettel at the start by reclaiming first place on track in a final-stint battle that included the pair banging wheels.
Valtteri Bottas, who won the last race in Russia, was on course to finish third, but retired when his Mercedes engine expired with a third of the race to go.
He had earlier triggered the exits of last year's Spanish GP lead combatants Max Verstappen and Kimi Raikkonen, when he tagged Raikkonen into the first corner of the race and sent the Ferrari sideways into Verstappen's Red Bull.
While Bottas continued, Raikkonen and Verstappen were out.
Ahead of that incident, Vettel had made a sensational getaway from second on the grid to move ahead of Hamilton into Turn 1.
Vettel went about gradually increasing his lead before pitting for another set of softs at the end of lap 14 of 66 and suffering a minor delay on the front left.
Mercedes decided to keep Hamilton out, and he immediately pumped in the fastest lap of the race and stretched his stint by an extra five laps.
When he did come in, Hamilton took on the medium tyres - and he was helped by Bottas staying out even longer and holding Vettel up for a spell until a bold pass in which Ferrari jinked from one side of the pits straight to the other and brushed the grass before completing the move.
The race swung back to Hamilton when Mercedes pitted him just as a virtual safety car - caused when Stoffel Vandoorne ended up in the gravel at Turn 1 after banging wheels with Felipe Massa - ended.
Hamilton rejoined on soft tyres, 24s behind Vettel.
Ferrari immediately pitted Vettel for mediums on the next lap, but Hamilton was alongside as he rejoined.
Hamilton kept his foot in and tried to go around the outside but Vettel squeezed him and they touched, with Hamilton taking to the run-off.
He regrouped, got back on the attack and five laps later, the move was done with DRS help around the outside into Turn 1.
Hamilton then controlled the gap to Vettel behind to take his second victory of the season.
Behind the lonely Ricciardo, Force India continued its impressive start to the season with Sergio Perez fourth and Esteban Ocon a career-best fifth.
Nico Hulkenberg secured his best result for Renault with sixth.
A stunning drive on a unique one-stop strategy brought Pascal Wehrlein up to seventh on the road for Sauber.
Although a five-second time penalty for failing to stay to the correct side of the pit entry bollard meant he lost that position to Carlos Sainz Jr, Wehrlein's late burst of pace meant he still secured eighth rather than falling right out of the points as initially seemed likely.
Kvyat and Romain Grosjean completed the top 10.
After his heroic qualifying performance, Fernando Alonso immediately fell back from his seventh place having made contact with Felipe Massa at the first corner.
While Massa had to pit with a puncture, Alonso continued in 11th but his pace faded later on before a late third stop for fresh softs allowed him to charge back to 12th - his first race finish of 2017.
RESULTS - 66 LAPS:
Source : https://au.eurosport.com/formula-1/spanish-grand-prix/2017/hamilton-denies-vettel-in-spanish-gp-thriller_sto6165746/story.shtml