App Developers try to avoid IPv6, I know. We cannot force them to use IPv6. But we have to force them to use libraries that work with IPv6 and IPv4, not some old stuff useing IPv4 only. Then there is golden rule: no IP addresses in code or config files, use names
My experience is that for simple applications, if you avoid hardcoding addresses then using just address family agnostic library calls (getnameinfo and getaddrinfo) should result in something that support s both IPv4 and IPv6. Note that getnameinfo and getaddrinfo can do a lot of stuff if you pass the right flags. Obviously in languages other than C that only works if equivalent functions are available. However, for logging it is common to log <addr>:<port>. Obviously if <addr> is an IPv6 address then you get something that is hard to parse later. IPv6 addresses are logged as [<ipv6-addr>]:<port>. Typically you want -4 and -6 switches. Though you can work around that in DNS. Many applications have some kinds of ACLs or interface configuration. Again parsing <addr>:<port> doesn't work in IPv6. It gets more tricky when an IPv6 socket can receive both IPv4 and IPv6 connections or packets. In that case you may end up with IPv4-mapped addresses (::ffff:<ipv4-address>). Note that the presentation format has dots. A bonus from IPv6, you can bind to ::%<interface-name> to specify a specific interface to use.