The 31-year-old joined Dortmund from Bayer Leverkusen for €11m in 2015 and made a positive impact in his first season, scoring three goals in 25 Bundesliga appearances as the club finished second, 10 points behind Bayern Munich.
Since then, Castro's performances have dwindled and he started only 16 of the club's league matches as they finished fourth in 2017-18, and he joins Stuttgart on the back of their successful seventh-placed finish on their first season back in the Bundesliga.
A message on Dortmund's official Twitter account read: "Thanks for everything, 'Gonzo'!
"Our number 27 leaves the BVB after three years and moves to VfB Stuttgart.
"We wish you all the best!"
Meanwhile, Stuttgart have allowed striker Daniel Ginczek to move to Wolfsburg on a four-year deal.
The 27-year-old target man, who began his career at Dortmund and enjoyed his most prolific spell at St. Pauli, scored seven goals in 17 starts in the Bundesliga in 2017-18.