Don't upgrade Python or Java until you run 5 commands. Each one below lists the deprecated calls your code still makes, and deprecated today usually means removed in the next major.
The problem with deprecations isn't that tools hide them. It's that they're warnings, and warnings scroll past in CI where nobody reads them. So the trick in every language is the same: make them errors once, on a branch, and read the list.
Python
pytest -W error::DeprecationWarning Every DeprecationWarning raised while your tests run becomes a failure with a traceback pointing at your line. Run it on a branch; don't leave it on in main until the list is empty.
TypeScript / JavaScript
typescript-eslint has a rule that uses type information to flag anything marked @deprecated:
// eslint.config.js, with typed linting enabled rules: { "@typescript-eslint/no-deprecated": "error" } tsc alone won't fail on deprecations; your editor's strike-through is the only other hint.
Go
staticcheck -checks SA1019 ./... SA1019 reports uses of identifiers whose doc comment starts with Deprecated:.
Java
javac -Xlint:deprecation ... In Maven, add -Xlint:deprecation to the compiler plugin's compilerArgs; in Gradle, options.compilerArgs << "-Xlint:deprecation". Add -Werror for the one run where you want the list to fail the build.
Kotlin
The compiler already warns on calls to @Deprecated declarations. For one build, make warnings fatal:
kotlin { compilerOptions { allWarningsAsErrors.set(true) } } Kotlin deprecations often carry a ReplaceWith(...), and the IDE can apply it for you.
Then what
Fix the list before the upgrade, while the old and new names both still work. During the upgrade you're fixing deprecations and breaking changes at once, and you can't tell which is which.
This is also the check Versyka runs inside every fix: before editing, it reads the new version's deprecation notes for the functions your code calls, in all five languages.
Which language in your stack hides the most deprecation warnings?