Suppose a class needs multiple properties of the same type. I don't want a big constructor where the user can mess up the order of args (remember, they are of the same type, no compile-time errors)
My first advice is to rethink that design. Perhaps you can create custom classes for each argument that actually tell you at compile time what those objects are. For example, if you have email, create Email class. If you have user id, create UserId class and so on and so forth. This will definitiely increase safety of your code (order of args won't be an issue anymore) and brings few more advantages (e.g. changing the underlying type of id is now trivial). You have strongly typed language, make use of that.
However this is not always possible. For example "Range" class that accepts two integers. Creating a separate class for each end sounds a bit artificial. And you will need to validate them (left end lower than right) ultimately.
Should the build() method throw (and also contain a lot of validation logic, proportional to the number of args)?
Of course build() should validate parameters before returning the object. Generally it is an antipattern to construct and pass around invalid objects. If some book says otherwise: throw it away. Although it is likely that it is not what Effective Java says (I didn't read it though), nothing you wrote here suggests that.
Normally your builder would keep an "optional" field per each parameter. And during build() it checks whether required fields are set. If not returns/throws an error.
Personally I would return a "Result" object instead of throwing an exception. A "Result" object is an object that holds either the expected object or an error. I don't know much about Java, but it must have or allow such constructions, no? But using exceptions is also acceptable, although a bit weird to my taste (it is not an exceptional situation when something doesn't pass validation).
Those of you who read Bloch's Effective Java may recall that he recommended the builder pattern in case there are multiple optional fields in the class. Using it for required args may be seen as misapplication of the pattern.
I kind of agree with that. But it is not a big deal, you can still use builder pattern. It brings other advantages. In fact validation itself is a good example. Even though it can be done in constructor, sometimes it is messy if for example validation depends on some configurable parameter, e.g. "max email length" (will you pass that to constructor as well?). Plus constructors cannot return "Result", cannot be async (does Java support async/await?), and generally are limited. Another reason is that builders can reuse their own internal state, e.g. you can cache things, which is especially useful if those objects are immutable. And so on, and so on...
build()is allowed to throw. Remember, the builder pattern is supposed to be used for optional args