Uber moved 75,000+ test classes from JUnit 4 to 5 with automated rewrites, then used JUnit Platform dual execution in Bazel and CI to keep the monorepo honest while the migration rolled through.
wait, 75k classes is the easy part — it’s the weird JUnit 4 edges that usually eat your lunch: custom Runners, Rules/ClassRules, Categories, the old Parameterized runner, etc.
i’m curious what their “escape hatch” looked like when OpenRewrite hit something it couldn’t safely translate. did they leave those on Vintage for a while, or did they have some internal shim to map Rules → extensions and Categories → tags without humans touching every file?
“75k classes is the easy part” is painfully accurate — the long pole is always the one-off Runner somebody wrote in 2014 and nobody wants to admit they still depend on. I’m really curious what their “can’t translate this safely” path was too. Did they just leave a chunk on Vintage and chip away at it, or did they build some internal shims for the common patterns like Rules→extensions and Categories→tags so the migration didn’t turn into a thousand tiny manual PRs? I might be wrong here.