Indeed using java Exception for common cause is considered a bad practice, and as @Michael said, Exceptions must be exceptional, because
- they break flow control
- they are slow (more details here How slow are Java exceptions?)
- they do not mix with functional paradigm (where Java is in part going to with the addition of lamda-expressions
However, creating a custom object for wrapping validation data is a good thing and InvalidEmailAddressException can be turned into CheckedEmail:
import java.util.List;
import java.util.stream.Collectors;
public class EmailValidator {
    public List<CheckedEmail> validate(List<String> emailAddresses) {
        return emailAddresses.stream().map(this::validate).collect(Collectors.toList());
    }
    public CheckedEmail validate(String emailAddress) {
        String[] emailParts = emailAddress.toString().split( "@", 3 );
        final boolean valid;
        if ( emailParts.length != 2 ) {
            valid = false;
        } else {
            // More validation can go here using one or more regex
            valid = true;
        }
        return new CheckedEmail(emailAddress, valid);
    }
    public static final class CheckedEmail {
        private final String emailAddress;
        private final boolean valid;
        private CheckedEmail(String emailAddress, boolean valid) {
            this.emailAddress = emailAddress;
            this.valid = valid;
        }
        public String getEmailAddress() {
            return emailAddress;
        }
        public boolean isValid() {
            return valid;
        }
    }
}
This in turn can be tested quite easily (and improved with a parameterized test):
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
public class EmailValidatorTest {
    private final EmailValidator emailValidator = new EmailValidator();
    @Test
    public void invalid_email() {
        EmailValidator.CheckedEmail checkedEmail = emailValidator.validate("missing.an.at.symbol");
        assertThat(checkedEmail.isValid()).isFalse();
    }
    @Test
    public void valid_email() {
        EmailValidator.CheckedEmail checkedEmail = emailValidator.validate("at.symbol@present");
        assertThat(checkedEmail.isValid()).isTrue();
    }
    @Test
    public void multiple_email_addresses() {
        List<String> emailAddresses = Arrays.asList("missing.an.at.symbol", "at.symbol@present");
        List<EmailValidator.CheckedEmail> checkedEmails = emailValidator.validate(emailAddresses);
        assertThat(checkedEmails)
                .extracting(ce -> ce.getEmailAddress() + " " + ce.isValid())
                .containsExactly(
                        "missing.an.at.symbol false",
                        "at.symbol@present true");
    }
}
If somewhere the point is just to log this, then:
List<EmailValidator.CheckedEmail> checkedEmails = emailValidator.validate(emailAddresses);
checkedEmails.stream()
        .filter(ce -> !ce.isValid())
        .map(ce -> String.format("Email address [%s] is invalid", ce.getEmailAddress()))
        .forEach(logger::error);
Hope this helps !