I create a data class for each kind of data I use. I do not use "string" and "int" types, except within those data classes. My data classes have meaningful names, and are used in contexts where the class's particular constraints are meaningful.
For example, if you have a "score" field that is only meaningful with values between 1 and 100, then you have NO BUSINESS storing it in an "int" class, whose range is -2,147,483,648 to 2,147,483,647. If you do it, have fun trying to validate it redundantly everywhere you pass it, and have fun figuring out when and where you need to validate it, and have fun when you forget a spot and let invalid data sneak into your system, and have fun trying to explain to me how a bug that's not the programmer's fault is possible in software written for deterministic digital hardware.
When/Where to Validate:
If you are accepting a score from a text field in a UI, perform preliminary validation on it at the point of entry, giving error messages appropriate for that particular user interface. The preliminary validation can be as complex as you need it to be to provide meaningful feedback to the user.
Once you've performed the initial validation, stuff the data in an appropriate meaningful data class like "Score", where the constructor will perform the final/authoritative validation. This authoritative validation should be as simple and efficient as possible, but absolutely sufficient for guaranteeing the value is valid. It's a boolean validation; the data is either valid or an exception is thrown appropriate for a programmer to read. If your data class's validation code is, for example, simply a regular expression match, consider including the data class name, the regular expression string, and the data itself in the error message. All basic data classes should be immutable, like the string class itself; once constructed, it is guaranteed to be valid and remain so.
Your preliminary validation code may or may not leverage the data class's validation code. It depends on how detailed the data you're collecting is, and how much feedback you want to send to the UI. Similarly, more complex classes you create, which contain data classes as fields, should not be constructable unless they are valid. So you may need to write code that validates or attempts to construct each individual data field, catch the low-level construction errors, and handle them by providing more appropriate feedback to the UI. Sometimes, this feedback can involve coloring a field red and displaying an error message next to it. So as you can see, there are two kinds of validation, and one is clearly more complex out of necessity.