Year in Review: The 2025-26 NCAA Men’s Volleyball Season

Volleyball
Men's
Featured
Season Recap
A full spring season of NCAA men’s volleyball boxscore data, pulled through the self-hosted NCAA pipeline into BigQuery: the kills leaders, and how Hawaii beat UC Irvine for the national title.
Published

July 8, 2026

The men’s volleyball pipeline has now pulled a complete season: every Division I match from January through the national championship in early May, backfilled game by game into BigQuery via the self-hosted NCAA API proxy. This post is the first real analysis built on top of it.

One note on the data: about 6% of rows carry a blank player name despite having real stat values, a gap on NCAA’s own side for a small number of games rather than a pipeline bug (see the pipeline’s DEPLOY.md for the full explanation). Everything below filters those rows out.

Querying the season

from google.cloud import bigquery

client = bigquery.Client()

# NCAA's API returns team as its own 6-character code (e.g. "PEPPER"), not
# a school name. Tables and charts below keep the raw code as-is (matches
# how the hockey post's chart also uses raw team codes); this mapping is
# only used to spell out real school names in the prose. A code not in
# this dict falls back to showing the raw abbreviation rather than
# failing, in case a future re-render surfaces a school not covered yet.
TEAM_NAMES = {
    "PEPPER": "Pepperdine",
    "UC IRV": "UC Irvine",
    "BALLST": "Ball State",
    "CATAWB": "Catawba",
    "PENNST": "Penn State",
    "MCKEND": "McKendree",
    "EMMAN": "Emmanuel (Ga.)",
    "LEWIS": "Lewis",
    "HAWAII": "Hawaii",
    "UCLA": "UCLA",
    "USC": "USC",
}


def team_name(code):
    return TEAM_NAMES.get(code, code)


query = """
SELECT
  player_name,
  team,
  SUM(CAST(kills AS INT64)) AS kills,
  SUM(CAST(attack_attempts AS INT64)) AS attempts,
  SAFE_DIVIDE(
    SUM(CAST(kills AS INT64)) - SUM(CAST(attack_errors AS INT64)),
    SUM(CAST(attack_attempts AS INT64))
  ) AS hitting_pct,
  COUNT(DISTINCT game_id) AS games
FROM `maydaystats.ncaa_volleyball_men.boxscores`
WHERE player_name != ''
GROUP BY player_name, team
HAVING attempts >= 200
ORDER BY kills DESC
LIMIT 10
"""

leaders = client.query(query).to_dataframe()
leaders
player_name team kills attempts hitting_pct games
0 Cole Hartke PEPPER 452 937 0.352188 29
1 Andrej Jokanovic UC IRV 419 998 0.278557 30
2 Patrick Rogers BALLST 414 786 0.399491 31
3 Justin Arrowood CATAWB 397 938 0.210021 27
4 Sean Kelly UCLA 396 753 0.398406 31
5 Sean Harvey PENNST 388 806 0.328784 29
6 Zach Rama UCLA 386 803 0.332503 30
7 Dillon Klein USC 385 770 0.340260 27
8 Bryce Wetjen MCKEND 375 813 0.258303 29
9 Aidan Feeney EMMAN 374 880 0.244318 26

Cole Hartke of Pepperdine led the country with 452 kills, on a 0.352 hitting percentage across 29 matches. UC Irvine’s Andrej Jokanovic finished a close second in raw kills, but needed nearly 60 more swings to get there, a volume-over-efficiency trade that shows up again later in his team’s championship match.

The top ten, by kills

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(9, 5))
labels = leaders["player_name"] + " (" + leaders["team"] + ")"
ax.barh(labels[::-1], leaders["kills"][::-1], color="#2c3e50")
ax.set_xlabel("Kills")
ax.set_title("Top 10 Kills Leaders, 2025-26 Men's Season")
ax.spines[["top", "right"]].set_visible(False)
plt.tight_layout()
plt.show()
Figure 1: Top 10 kills leaders, 2025-26 NCAA D1 men’s season (min. 200 attempts)

Digs, blocks, and aces

digs_query = """
SELECT player_name, team, SUM(CAST(digs AS INT64)) AS total
FROM `maydaystats.ncaa_volleyball_men.boxscores`
WHERE player_name != ''
GROUP BY player_name, team
ORDER BY total DESC
LIMIT 1
"""
digs_leader = client.query(digs_query).to_dataframe()

blocks_query = """
SELECT player_name, team, SUM(CAST(total_blocks AS FLOAT64)) AS total
FROM `maydaystats.ncaa_volleyball_men.boxscores`
WHERE player_name != ''
GROUP BY player_name, team
ORDER BY total DESC
LIMIT 1
"""
blocks_leader = client.query(blocks_query).to_dataframe()

aces_query = """
SELECT player_name, team, SUM(CAST(service_aces AS INT64)) AS total
FROM `maydaystats.ncaa_volleyball_men.boxscores`
WHERE player_name != ''
GROUP BY player_name, team
ORDER BY total DESC
LIMIT 1
"""
aces_leader = client.query(aces_query).to_dataframe()

Nico Paula (Lewis) led all players in digs with 264. Owen Rose (Penn State) topped the block chart with 156, well clear of the field. And Andrew Rowan (UCLA) led in aces with 57.

The championship: Hawaii over UC Irvine

final_query = """
SELECT player_name, team, kills, attack_attempts, hitting_percentage, total_blocks
FROM `maydaystats.ncaa_volleyball_men.boxscores`
WHERE game_id = '6598557'
  AND CAST(kills AS INT64) > 0
ORDER BY team, CAST(kills AS INT64) DESC
"""
final_box = client.query(final_query).to_dataframe()
final_box
player_name team kills attack_attempts hitting_percentage total_blocks
0 Kristian Titriyski HAWAII 16 31 0.387 1
1 Adrien Roure HAWAII 15 31 0.323 0
2 Louis Sakanoko HAWAII 12 20 0.500 3
3 Justin Todd HAWAII 7 10 0.500 1
4 Trevell Jordan HAWAII 5 7 0.714 3
5 Tread Rosenthal HAWAII 3 6 0.167 3
6 Andrej Jokanovic UC IRV 13 35 0.229 3
7 William D'arcy UC IRV 9 24 0.292 0
8 Andreas Brinck UC IRV 6 18 0.111 1
9 Trevor Clark UC IRV 6 12 0.333 6
10 Micah Goss UC IRV 3 4 0.750 6
11 Cameron Kosty UC IRV 1 2 .000 5

Hawaii beat UC Irvine 3-1 on May 11, 2026 to win the national championship, the program’s first title since 2021. Kristian Titriyski led the way with 16 kills on a .387 hitting percentage, with Adrien Roure adding 15 more. Louis Sakanoko was the most efficient hitter on the floor, going 12-for-20 (.500) with 3 blocks off the bench.

UC Irvine’s offense leaned almost entirely on Andrej Jokanovic, the same volume hitter who finished second nationally in kills for the season. He had a team-high 13 kills in the final, but needed 35 attempts to get there, a .229 hitting percentage that never let UC Irvine pull away the way Hawaii’s more balanced, more efficient attack did.

What’s next

This covers the season at a high level: the national leaderboards and the championship match itself. The same table supports much narrower questions too, like a single team’s hitting efficiency by conference opponent, or how a freshman’s role grew over the season. Those are posts for another day, now that a full season of clean data is sitting in BigQuery.

The pipeline code behind this post lives here.

Note

Like the baseball and hockey posts, this one uses Quarto’s frozen execution (freeze: auto): the queries above ran once, locally, against BigQuery, and the deployed site reuses that committed output rather than re-querying on every build.