Unfortunately, the design of the Java language does not make it easy to swap out dependencies. While you could wrap external dependencies with interfaces and do some dependency injection, you can't use simple type aliases.
Ideally, you can do a lot of the migration by doing search and replace across the project. Because this is comparatively easy you might want to at least try if that works without using an iterative approach.
Otherwise, look at your dependency graph of the components you control and see which parts are fairly isolated and easy to migrate. You will have to convert between the time libraries at the API boundaries:
- you can keep the old API and convert any arguments/return values within the methods
- you can change the API and do the conversion at the call location
- you can sometimes do both: migrate the methods to a new API but offer compatibility wrappers with the old API
Which approach to use depends:
- if a method is used in many places it may be easier to use the compatibility wrapper approach so that you can migrate one call site at a time
- if you don't want to touch other components in this iteration, keep the old interface – but that means you will have to touch this code again in a second step
- otherwise, just do what you need and fix the call sites until it works without errors
Both a top-down or bottom-up approach can be fine. The above view of API boundaries has a bottom-up viewpoint, but the same principles apply in either case. I think that bottom-up integration will result in having to write fewer compatibility wrappers that you will have to delete later. However, this is very much a judgement call that you have to make.
If some components are tightly coupled, it can make a lot of sense to migrate them together.
In any case, you will likely want to write an utility class that helps with conversions between the two representations.