Validations are executed on the server side and page is updated with the result.
<div class="card">
    <h:form>
        <p:messages id="msgs"/>
        <h:panelGrid columns="3" cellpadding="7" styleClass="mb-3">
            <p:outputLabel for="firstname" value="Firstname:"/>
            <p:inputText id="firstname" value="#{remoteValidationView.firstname}" required="true" label="Firstname">
                <f:validateLength minimum="2"/>
            </p:inputText>
            <p:message for="firstname" display="icon"/>
            <p:outputLabel for="lastname" value="Lastname:"/>
            <p:inputText id="lastname" value="#{remoteValidationView.lastname}" label="Lastname" required="true">
                <f:validateLength minimum="2"/>
                <p:ajax update="msgLastname" event="keyup"/>
            </p:inputText>
            <p:message for="lastname" id="msgLastname" display="icon"/>
        </h:panelGrid>
        <p:commandButton value="Save" update="@form" action="#{remoteValidationView.save}" icon="pi pi-check" />
    </h:form>
</div>
package org.primefaces.showcase.view.ajax;
import jakarta.enterprise.context.RequestScoped;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Named;
@Named
@RequestScoped
public class RemoteValidationView {
    private String firstname;
    private String lastname;
    public String getFirstname() {
        return firstname;
    }
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
    public String getLastname() {
        return lastname;
    }
    public void setLastname(String lastname) {
        this.lastname = lastname;
    }
    public void save() {
        FacesContext.getCurrentInstance().addMessage(null,
                new FacesMessage("Welcome " + firstname + " " + lastname));
    }
}