Timeline supports lazy loading of events during moving / zooming in the timeline. This makes sense when event's loading is time-consuming. Events are loaded lazy when p:ajax with event="lazyload" is attached to the p:timeline tag. The event class TimelineLazyLoadEvent contains one or two time ranges the events should be loaded for (two times ranges occur when you zoom out the timeline).
Note: The "lazyload" listener is not invoked again when the visible time range (incl. some hidden ranges defined by preloadFactor) already has lazy loaded events.
<div class="card">
    <h:form id="form">
        <p:growl id="growl" showSummary="true" showDetail="false">
            <p:autoUpdate/>
        </p:growl>
        <div id="loadingText" style="font-weight:bold; margin:-5px 0 5px 0; visibility:hidden;">Loading ...
        </div>
        <p:timeline id="timeline" value="#{lazyTimelineView.model}"
                    preloadFactor="#{lazyTimelineView.preloadFactor}"
                    zoomMax="#{lazyTimelineView.zoomMax}" minHeight="170">
            <p:ajax event="lazyload" update="@none" listener="#{lazyTimelineView.onLazyLoad}"
                    onstart="$('#loadingText').css('visibility', 'visible')"
                    oncomplete="$('#loadingText').css('visibility', 'hidden')"/>
        </p:timeline>
        <h:panelGrid columns="2" style="margin-top:15px">
            <p:spinner id="spinner" value="#{lazyTimelineView.preloadFactor}"
                       min="0" max="1" stepFactor="0.05"/>
            <p:commandButton value="Update Preload Factor" process="@this spinner" update="timeline"
                             action="#{lazyTimelineView.clearTimeline}"
                             style="margin-left:5px"/>
        </h:panelGrid>
    </h:form>
</div>
package org.primefaces.showcase.view.data.timeline;
import org.primefaces.component.timeline.TimelineUpdater;
import org.primefaces.event.timeline.TimelineLazyLoadEvent;
import org.primefaces.model.timeline.TimelineEvent;
import org.primefaces.model.timeline.TimelineModel;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.PrimitiveIterator;
import java.util.Random;
import jakarta.annotation.PostConstruct;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
@Named
@ViewScoped
public class LazyTimelineView implements Serializable {
    private TimelineModel<String, ?> model;
    private float preloadFactor = 0;
    private long zoomMax;
    @PostConstruct
    protected void initialize() {
        // create empty model
        model = new TimelineModel<>();
        // about five months in milliseconds for zoomMax
        // this can help to avoid a long loading of events when zooming out to wide time ranges
        zoomMax = 1000L * 60 * 60 * 24 * 31 * 5;
    }
    public TimelineModel<String, ?> getModel() {
        return model;
    }
    public void onLazyLoad(TimelineLazyLoadEvent e) {
        try {
            // simulate time-consuming loading before adding new events
            Thread.sleep((long) (1000 * Math.random() + 100));
        }
        catch (InterruptedException ex) {
            // ignore
        }
        TimelineUpdater timelineUpdater = TimelineUpdater.getCurrentInstance(":form:timeline");
        LocalDateTime startDate = e.getStartDateFirst(); // alias getStartDate() can be used too
        LocalDateTime endDate = e.getEndDateFirst(); // alias getEndDate() can be used too
        // fetch events for the first time range
        generateRandomEvents(startDate, endDate, timelineUpdater);
        if (e.hasTwoRanges()) {
            // zooming out ==> fetch events for the second time range
            generateRandomEvents(e.getStartDateSecond(), e.getEndDateSecond(), timelineUpdater);
        }
    }
    private void generateRandomEvents(LocalDateTime startDate, LocalDateTime endDate, TimelineUpdater timelineUpdater) {
        LocalDateTime curDate = startDate;
        Random rnd = new Random();
        PrimitiveIterator.OfInt randomInts = rnd.ints(1, 99999).iterator();
        while (curDate.isBefore(endDate)) {
            // create events in the given time range
            if (rnd.nextBoolean()) {
                // event with only one date
                model.add(TimelineEvent.<String>builder()
                        .data("Event " + randomInts.nextInt())
                        .startDate(curDate)
                        .build(), timelineUpdater);
            }
            else {
                // event with start and end dates
                model.add(TimelineEvent.<String>builder()
                        .data("Event " + randomInts.nextInt())
                        .startDate(curDate)
                        .endDate(curDate.plusHours(18))
                        .build(),
                        timelineUpdater);
            }
            curDate = curDate.plusHours(24);
        }
    }
    public void clearTimeline() {
        // clear Timeline, so that it can be loaded again with a new preload factor
        model.clear();
    }
    public void setPreloadFactor(float preloadFactor) {
        this.preloadFactor = preloadFactor;
    }
    public float getPreloadFactor() {
        return preloadFactor;
    }
    public long getZoomMax() {
        return zoomMax;
    }
}