I keep coming back to java.util.ServiceLoader. I have used it to put a JSON layer behind a contract, so the core code carries no direct dependency on any particular JSON library, and I can swap the implementation without touching callers. The same shape works for JWT handling, where the concrete library might be jose4j or another JOSE implementation, and you can easily find other decoupling use-cases.