Skip to content

Code Examples 4: Converting to and from Other Formats

Sean C Foley edited this page May 27, 2023 · 12 revisions

Produce a Variety of Strings of an Address for Text Search

strings("ffff::a000:0/108");
strings("ffff::8fff:ffff:0:ffff");	

static void strings(String str) {
	IPAddress addr = new IPAddressString(str).getAddress();
	String strings[] = addr.toStandardStrings();
	System.out.println(strings.length + " strings:\n" + 
		String.join(",\n", strings) + '\n');
}

Output:

18 strings:
ffff:0000:0000:0000:0000:0000:160.000.000.000/108,
FFFF:0:0:0:0:0:160.000.000.000/108,
FFFF:0:0:0:0:0:A000:0/108,
ffff::a000:0/108,
FFFF::160.000.000.000/108,
FFFF:0000:0000:0000:0000:0000:160.000.000.000/108,
FFFF:0000:0000:0000:0000:0000:160.0.0.0/108,
ffff::160.0.0.0/108,
FFFF::160.0.0.0/108,
ffff:0:0:0:0:0:160.000.000.000/108,
FFFF::A000:0/108,
ffff:0000:0000:0000:0000:0000:160.0.0.0/108,
FFFF:0:0:0:0:0:160.0.0.0/108,
ffff:0:0:0:0:0:a000:0/108,
ffff:0000:0000:0000:0000:0000:a000:0000/108,
ffff::160.000.000.000/108,
FFFF:0000:0000:0000:0000:0000:A000:0000/108,
ffff:0:0:0:0:0:160.0.0.0/108

24 strings:
FFFF:0000:0000:0000:8FFF:FFFF:0000:FFFF,
ffff::8fff:ffff:0:ffff,
ffff:0:0:0:8fff:ffff:000.000.255.255,
FFFF::8FFF:FFFF:000.000.255.255,
FFFF:0000:0000:0000:8FFF:FFFF:000.000.255.255,
ffff:0000:0000:0000:8fff:ffff:0000:ffff,
ffff:0:0:0:8fff:ffff:0.0.255.255,
ffff:0:0:0:8fff:ffff:0:ffff,
ffff:0000:0000:0000:8fff:ffff:000.000.255.255,
FFFF:0000:0000:0000:8FFF:FFFF::FFFF,
FFFF:0000:0000:0000:8FFF:FFFF:0.0.255.255,
ffff::8fff:ffff:0000:ffff,
FFFF:0:0:0:8FFF:FFFF:0.0.255.255,
ffff:0000:0000:0000:8fff:ffff:0.0.255.255,
FFFF:0:0:0:8FFF:FFFF:000.000.255.255,
ffff::8fff:ffff:0.0.255.255,
FFFF:0:0:0:8FFF:FFFF:0:FFFF,
ffff:0:0:0:8fff:ffff::ffff,
FFFF:0:0:0:8FFF:FFFF::FFFF,
FFFF::8FFF:FFFF:0:FFFF,
FFFF::8FFF:FFFF:0000:FFFF,
ffff:0000:0000:0000:8fff:ffff::ffff,
ffff::8fff:ffff:000.000.255.255,
FFFF::8FFF:FFFF:0.0.255.255

Convert to/from Binary String from/to IP Address

String str = "2001:db8:85a3::8a2e:370:7334";
IPAddressString addrStr = new IPAddressString(str);
IPAddress addr  = addrStr.getAddress();
String binaryStr = addr.toBinaryString();
System.out.println(binaryStr);

Output:

00100000000000010000110110111000100001011010001100000000000000000000000000000000100010100010111000000011011100000111001100110100

Parse it as binary, which requires a "0b" string prefix:

addrStr = new IPAddressString(Address.BINARY_PREFIX + binaryStr);
addr = addrStr.getAddress();
System.out.println(addr);

Output:

2001:db8:85a3::8a2e:370:7334

You can preserve the segments when printing:

binaryStr = addr.toSegmentedBinaryString();
System.out.println(binaryStr);

Output:

0b0010000000000001:0b0000110110111000:0b1000010110100011:0b0000000000000000:0b0000000000000000:0b1000101000101110:0b0000001101110000:0b0111001100110100

Parse it as binary segments:

addrStr = new IPAddressString(binaryStr);
addr = addrStr.getAddress();
System.out.println(addr);

Output:

2001:db8:85a3::8a2e:370:7334

Convert to/from IPv6 Address from/to MAC Address

// start with a /64 prefix and a mac address
IPv6Address subnet = new IPAddressString("1111:2222:3333:4444::/64").
	getAddress().toIPv6();
MACAddress mac = new MACAddressString("aa:bb:cc:dd:ee:ff").getAddress();
		
// break into the components and combine ourselves
IPv6AddressSection prefix = subnet.getNetworkSection();
IPv6AddressSection macConverted = mac.toEUI64IPv6();
IPv6Address converted = new IPv6Address(prefix.append(macConverted));
System.out.println("combined " + subnet + " with " + mac + " resulting in " + 
	converted);

// or use a shortcut
IPv6Address convertedAgain = new IPv6Address(subnet, mac);
System.out.println("combined " + subnet + " with " + mac + " resulting in " + 
	convertedAgain);
				
// back to mac again
MACAddress macAgain = converted.toEUI(false);
System.out.println("extracted " + macAgain);
		
// convert to the link-local IPv6 address
IPv6Address linkLocal = macAgain.toLinkLocalIPv6();
System.out.println("converted " + mac + " to link local " + linkLocal);

Output:

combined 1111:2222:3333:4444::/64 with aa:bb:cc:dd:ee:ff resulting in 1111:2222:3333:4444:a8bb:ccff:fedd:eeff/64
combined 1111:2222:3333:4444::/64 with aa:bb:cc:dd:ee:ff resulting in 1111:2222:3333:4444:a8bb:ccff:fedd:eeff/64
extracted aa:bb:cc:dd:ee:ff
converted aa:bb:cc:dd:ee:ff to link local fe80::a8bb:ccff:fedd:eeff

Convert to/from IPv6 address from/to Ascii Base 85 Encoding

Storing an IPv6 address, with no scope or zone, takes 16 bytes. When using ascii chars with conventional notation, it takes anywhere from two bytes ("::") to 45 bytes ("aaaa:bbbb:cccc:dddd:eeee:ffff:255.255.255.255"), typically extending to 39 bytes ("aaa:bbbb:cccc:dddd:eeee:ffff:aaaa:bbbb"). RFC 1924 describes a compact representation of IPv6 addresses using base 85 digits, implemented by this library. Using a base 85 ascii string takes 20 bytes, not much more than using bytes directly.

IPv6Address addr = new IPAddressString("102:304:506:708:90a:b0c:d0e:fff").
	toAddress().toIPv6();
				
// to bytes and back
byte bytes[] = addr.getBytes();
addr = new IPv6Address(bytes);
print(addr, bytes);
		
// to ascii bytes and back
Charset utf8 = StandardCharsets.UTF_8;
String base85Str = addr.toBase85String();
System.out.println("base 85 string is " + base85Str);
bytes = base85Str.getBytes(utf8);
addr = new IPAddressString(new String(bytes, utf8)).toAddress().toIPv6();
print(addr, bytes);

static void print(IPAddress addr, byte bytes[]) {
	System.out.println("got " + addr + " from byte[] " + 
		Arrays.toString(bytes) + " of length " + bytes.length);
}

Output:

got 102:304:506:708:90a:b0c:d0e:fff from byte[] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -1] of length 16
base 85 string is 0O|s)GT}-*WUn!Z$mO4Z
got 102:304:506:708:90a:b0c:d0e:fff from byte[] [48, 79, 124, 115, 41, 71, 84, 125, 45, 42, 87, 85, 110, 33, 90, 36, 109, 79, 52, 90] of length 20

Convert Subnet Members to Integers using Filter, Map and Collect Stream Operations

In this example, subnets are first filtered as IPv4 or IPv6, then IPv4 operations use longs while IPv6 operations use BigInteger to provide the integer values.

boolean inParallel = true;
collectInts(new String[] {"1.2.3.8/29", "2.2.3.8/29", "::/124", "1::/124"},
	inParallel);
	
static Stream<? extends IPAddress> toStream(List<? extends IPAddress> subnetList,
		boolean inParallel) {
	Stream<? extends IPAddress> joinedStream =
		AddressComponentRange.stream(IPAddress::stream, subnetList);
	if(inParallel) {
		joinedStream = joinedStream.parallel();
	}
	return joinedStream;
}
	
static void collectInts(String strs[], boolean inParallel) {
	System.out.println("Converting to integers the following " + strs.length +
		" subnets: " + Arrays.toString(strs));
	List<? extends IPAddress> subnets = 
			Arrays.stream(strs).
			map(IPAddressString::new).
			map(IPAddressString::getAddress).
			collect(Collectors.toList());
	List<Long> ipv4s = toStream(subnets, inParallel).
			filter(IPAddress::isIPv4).
			map(IPAddress::toIPv4).
			mapToLong(IPv4Address::longValue).boxed().
			collect(Collectors.toList());
	List<BigInteger> ipv6s = toStream(subnets, inParallel).
			filter(IPAddress::isIPv6).
			map(IPAddress::getValue).
			collect(Collectors.toList());
	System.out.println("IPv4 addresses as unsigned ints: " + ipv4s);
	System.out.println("IPv6 addresses as unsigned ints: " + ipv6s);
}

Output:

Converting to integers the following 4 subnets: [1.2.3.8/29, 2.2.3.8/29, ::/124, 1::/124]
IPv4 addresses as unsigned ints: [16909064, 16909065, 16909066, 16909067, 16909068, 16909069, 16909070, 16909071, 33686280, 33686281, 33686282, 33686283, 33686284, 33686285, 33686286, 33686287]
IPv6 addresses as unsigned ints: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 5192296858534827628530496329220096, 5192296858534827628530496329220097, 5192296858534827628530496329220098, 5192296858534827628530496329220099, 5192296858534827628530496329220100, 5192296858534827628530496329220101, 5192296858534827628530496329220102, 5192296858534827628530496329220103, 5192296858534827628530496329220104, 5192296858534827628530496329220105, 5192296858534827628530496329220106, 5192296858534827628530496329220107, 5192296858534827628530496329220108, 5192296858534827628530496329220109, 5192296858534827628530496329220110, 5192296858534827628530496329220111]

Convert to/from IPv4 Address from/to IPv6 Address

The library provides numerous common standard conversions between IPv4 and IPv6, if you are using a tunneling solution or other IPv4/v6 transition mechanism for managing IPv4/IPv6 integration. Such conversion methods generally insert an IPv4 address into the lower 4 bytes of a 16-byte IPv6 address.

Starting with an IPv6 address, the following convert method checks for various conversion formats, such as Teredo, 6 to 4, 6 over 4, Isatap, IPv4-translation, and IPv4-mapping. It returns the converted address and an instance of ConversionFlags that indicates how the IPv4 address was converted from IPv6, so that it can be converted back again.

static class ConvertedIPv4Address {
	IPv4Address address;
	ConversionFlags flags;
}
		
static ConvertedIPv4Address convert(IPv6Address ipv6Address)  {
	IPv4Address result;
	ConversionFlags flags= new ConversionFlags();
	if(flags.isTeredo = ipv6Address.isTeredo()) {
		IPv4AddressSection section = new IPv4AddressSection(~ipv6Address.getEmbeddedIPv4Address().intValue());
		result = ipv6Address.getIPv4Network().getAddressCreator().createAddress(section);
		flags.teredoServer = ipv6Address.getEmbeddedIPv4Address(4);
		flags.teredoFlags = ipv6Address.getSegment(4).getSegmentValue();
		flags.teredoPort = ~ipv6Address.getSegment(5).getSegmentValue();
	} else if(flags.is6To4 = ipv6Address.is6To4()) {
		result = ipv6Address.get6To4IPv4Address();
	} else if((flags.is6Over4 = ipv6Address.is6Over4()) || 
			(flags.isIsatap = ipv6Address.isIsatap()) || 
			(flags.isIPv4Translatable = ipv6Address.isIPv4Translatable()) ||
			(flags.isIPv4Mapped = ipv6Address.isIPv4Mapped())) {
		result = ipv6Address.getEmbeddedIPv4Address();
	} else if(ipv6Address.isLoopback()) {
		result = ipv6Address.getIPv4Network().getLoopback();
	} else {
		return null;
	}
	ConvertedIPv4Address converted = new ConvertedIPv4Address();
	converted.address = result;
	converted.flags = flags;
	return converted;
}

static class ConversionFlags {
	boolean isTeredo, is6To4, is6Over4, isIsatap, isIPv4Translatable, isIPv4Mapped;
		
	int teredoFlags, teredoPort;
	IPv4Address teredoServer;
		
	/**
	 * Based on the flags, converts an IPv4 address to IPv6
	 */
	IPv6Address toIPv6(IPv4Address address) {
		IPv6AddressCreator creator = address.getIPv6Network().getAddressCreator();
		IPv6AddressSegment zero = creator.createSegment(0);
		IPv6AddressSegment segs[];
		if(isTeredo) {
			segs = new IPv6AddressSegment[IPv6Address.SEGMENT_COUNT];
			segs[0] = creator.createSegment(0x2001);
			segs[1] = zero;
			segs[2] = teredoServer.getSegment(0).join(creator, teredoServer.getSegment(1));
			segs[3] = teredoServer.getSegment(2).join(creator, teredoServer.getSegment(3));
			segs[4] = creator.createSegment(teredoFlags);
			segs[5] = creator.createSegment(~teredoPort);
			int embeddedVal = ~address.intValue();
			segs[6] = creator.createSegment(embeddedVal >>> 16);
			segs[7] = creator.createSegment(embeddedVal & 0xffff);
			return creator.createAddress(creator.createSection(segs));
		} else if(is6Over4) {
			segs = new IPv6AddressSegment[IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT];
			segs[1] = segs[2] = segs[3] = segs[4] = segs[5] = zero;
			segs[0] = creator.createSegment(0xfe80);
			return address.getIPv6Address(creator.createSection(segs));
		} else if(isIsatap) {
			segs = new IPv6AddressSegment[IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT];
			segs[1] = segs[2] = segs[3] = segs[4] = zero;
			segs[0] = creator.createSegment(0xfe80);
			segs[5] = creator.createSegment(0x5efe);
			return address.getIPv6Address(creator.createSection(segs));
		} else if(isIPv4Translatable) {
			segs = new IPv6AddressSegment[IPv6Address.MIXED_ORIGINAL_SEGMENT_COUNT];
			segs[0] = segs[1] = segs[2] = segs[3] = segs[5] = zero;
			segs[4] = creator.createSegment(0xffff);
			return address.getIPv6Address(creator.createSection(segs));
		} else if(is6To4) {
			segs = new IPv6AddressSegment[IPv6Address.SEGMENT_COUNT];
			segs[0] = creator.createSegment(0x2002);
			segs[1] = address.getSegment(0).join(creator, address.getSegment(1));
			segs[2] = address.getSegment(2).join(creator, address.getSegment(3));
			segs[3] = segs[4] = segs[5] = segs[6] = segs[7] = zero;
			return creator.createAddress(creator.createSection(segs));
		} else if(isIPv4Mapped) {
			return address.getIPv4MappedAddress();
		} else if(address.isLoopback()) {
			return address.getIPv6Network().getLoopback();
		}
		// default conversion is IPv4-mapped
		return address.getIPv4MappedAddress();
	}
}

Here we show some sample code demonstrating the various conversions.

String addressStrs[] = {
	"1.2.3.4",
	"a:b:c:d:e:f:a:b",
	"::ffff:a:b", // IPv4-mapped
	"::1",
	"2002:c000:0204::", // 6 to 4 192.0.2.4
	"2001:0000:4136:e378:8000:63bf:3fff:fdd2", // Teredo, 192.0.2.45
	"fe80::192.0.2.142", // 6 over 4
	"::ffff:0:192.0.2.4", // IPv4-translatable
	"fe80::0000:5efe:192.0.2.143" // Isatap
};
for(String addrStr : addressStrs) {
	IPAddress address = new IPAddressString(addrStr).getAddress();
	IPAddress converted = null, convertedBack = null;
	if(address.isIPv4()) {
		IPv6Address convertedAddress = address.toIPv4().getIPv4MappedAddress();
		converted = convertedAddress;
		convertedBack = convertedAddress.getEmbeddedIPv4Address();
	} else {
		ConvertedIPv4Address convertedAddress = convert(address.toIPv6());
		if(convertedAddress != null) {
			converted = convertedAddress.address;
			convertedBack = convertedAddress.flags.toIPv6(convertedAddress.address);
		}
	}
	System.out.println("\nstarting with " + address);
	if(converted != null) {
		System.out.println("converted to " + converted);
		if(convertedBack != null) {
			System.out.println("converted back to " + convertedBack);
		} else {
			System.out.println("not convertible back");
		}
	} else {
		System.out.println("not convertible");
	}
}

Output:

starting with 1.2.3.4
converted to ::ffff:102:304
converted back to 1.2.3.4

starting with a:b:c:d:e:f:a:b
not convertible

starting with ::ffff:a:b
converted to 0.10.0.11
converted back to ::ffff:a:b

starting with ::1
converted to 127.0.0.1
converted back to ::1

starting with 2002:c000:204::
converted to 192.0.2.4
converted back to 2002:c000:204::

starting with 2001:0:4136:e378:8000:63bf:3fff:fdd2
converted to 192.0.2.45
converted back to 2001:0:4136:e378:8000:63bf:3fff:fdd2

starting with fe80::c000:28e
converted to 192.0.2.142
converted back to fe80::c000:28e

starting with ::ffff:0:c000:204
converted to 192.0.2.4
converted back to ::ffff:0:c000:204

starting with fe80::5efe:c000:28f
converted to 192.0.2.143
converted back to fe80::5efe:c000:28f

Extend Address Classes for Customized Conversion to/from IPv4 Address from/to IPv6 Address

Adding to the code from the previous example, here we extend the IPAddress IPv4 and IPv6 address types to make customized IPv4/v6 conversion automatic. Extending the type system allows for conversion with no explicit calls to external conversion routines required, instead using the built-in toIPv4/v6() methods, with the conversion rules governed by the instances themselves, thus reducing the potential for error. The IPv4 addresses converted from IPv6 will remember details regarding how to convert back to IPv6.

All address creation within the IPAddress library goes through the network address creator instances. Providing your own network instances allows you to insert your own address types. You override the address creation methods to create your own address instances. In the creator types there are a few methods that create address instances, but for the purposes of this example we need override only one method in each, the one used from most places in the library taking an address section as its only argument.

static IPv4AddressNetwork myIPv4Network = new IPv4AddressNetwork() {
		
	@Override
	protected IPv4AddressCreator createAddressCreator() {
		return new IPv4AddressCreator(myIPv4Network) {
				
			@Override
			public IPv4Address createAddress(
					IPv4AddressSection section) {
				return new MyIPv4Address(section);
			}
		};
	}
};
	
static IPv6AddressNetwork myIPv6Network = new IPv6AddressNetwork() {
		
	@Override
	protected IPv6AddressCreator createAddressCreator() {
		return new IPv6AddressCreator(this) {

			@Override
			public IPv6Address createAddress(
					IPv6AddressSection section) {
				return new MyIPv6Address(section);
			}
		};
	}
};

When extending the address classes, you can override the getNetwork, getIPv4Network, and getIPv6Network methods to return your own network instances. For the purpose of using our customized IPv4/v6 address conversion, we also override the isIPv4/v6Convertible() and toIPv4/v6() methods and put the conversion code in the toIPv4/v6() methods. We reuse the conversion code from the previous example.

The extension of IPv6Address:

static class MyIPv6Address extends IPv6Address {
		
	public MyIPv6Address(IPv6AddressSection section) {
		super(section);
	}
		
	public MyIPv6Address(IPv6AddressSegment segments[]) {
		super(segments);
	}
		
	@Override
	public IPv6AddressNetwork getNetwork() {
		return myIPv6Network;
	}
		
	@Override
	public IPv4AddressNetwork getIPv4Network() {
		return myIPv4Network;
	}
		
	@Override
	public boolean isIPv4Convertible() {
		return isTeredo() ||
				is6To4() ||
				is6Over4() ||
				isIsatap() ||
				isIPv4Translatable() ||
				isLoopback() ||
				super.isIPv4Convertible();
	}
		
	@Override
	public IPv4Address toIPv4() {
		ConvertedIPv4Address converted = convert(this);
		if(converted == null) {
			return null;
		}
		MyIPv4Address result = (MyIPv4Address) converted.address;
		result.flags = converted.flags;
		return result;
	}
		
	private String typeStr() {
		return isTeredo() ? "Teredo " : 
			is6To4() ? "6 To 4 " : 
			is6Over4() ? "6 Over 4 " : 
			isIsatap() ? "Isatap " : 
			isIPv4Translatable() ? "IPv4-translated " :
			isLoopback() ? "loopback " :
			isIPv4Mapped() ? "IPv4-mapped " : "";
	}
		
	@Override
	public String toString() {
		return typeStr() + getClass().getSimpleName() + ' ' +
			super.toString();
	}
}

The extension of IPv4Address:

static class MyIPv4Address extends IPv4Address {
	ConversionFlags flags;
		
	public MyIPv4Address(IPv4AddressSection section) {
		super(section);
	}
		
	public MyIPv4Address(int address) {
		super(address);
	}
		
	@Override
	public IPv4AddressNetwork getNetwork() {
		return myIPv4Network;
	}
		
	@Override
	public IPv6AddressNetwork getIPv6Network() {
		return myIPv6Network;
	}
		
	@Override
	public boolean isIPv6Convertible() {
		// we can always convert, it is just a matter of how
		return true;
	}
		
	@Override
	public IPv6Address toIPv6() {
		if(flags == null) {
			if(isLoopback()) {
				return getIPv6Network().getLoopback();
			}
			return super.toIPv6();
		}
		return flags.toIPv6(this);
	}
		
	@Override
	public String toString() {
		return getClass().getSimpleName() + ' ' + super.toString();
	}
}

String parameters allow you to select the network to use for address creation:

static final IPAddressStringParameters params =
	new IPAddressStringParameters.Builder().
		getIPv6AddressParametersBuilder().
			setNetwork(myIPv6Network).getParentBuilder().
		getIPv4AddressParametersBuilder().
			setNetwork(myIPv4Network).getParentBuilder().
	toParams();

We extend IPAddressString for our own type that will always use the configured string parameters:

static class MyIPAddressString extends IPAddressString {
	public MyIPAddressString(String addr) {
		super(addr, params);
	}
}

Here we show some sample code demonstrating the conversion types. We show both the default standard conversion provided by IPAddress that uses IPv4-mapped IPv6 addresses, and we show the custom types using their own conversion, simply by choosing the custom MyIPAddressString to create the addresses.

String addressStrs[] = {
	"1.2.3.4",
	"::ffff:a:b", // IPv4-mapped
	"a:b:c:d:e:f:a:b",
	"::1",
	"2002:c000:0204::", // 6 to 4 192.0.2.4
	"2001:0000:4136:e378:8000:63bf:3fff:fdd2", // Teredo, 192.0.2.45
	"fe80::192.0.2.142", // 6 over 4
	"::ffff:0:192.0.2.4", // IPv4-translatable
	"fe80::0000:5efe:192.0.2.143" // Isatap
};
System.out.println("Using standard IPv4-mapped conversion");
for(String addrStr : addressStrs) {
	convert(addrStr, IPAddressString::new);
}
System.out.println("\n\nUsing customized conversion");
for(String addrStr : addressStrs) {
	convert(addrStr, MyIPAddressString::new);
}

static void convert(String str, Function<String, IPAddressString> creator) {
	IPAddress address = creator.apply(str).getAddress();
	if(address.isIPv4()) {
		convertAndBack(address, IPAddress::toIPv6, IPAddress::toIPv4);
	} else {
		convertAndBack(address, IPAddress::toIPv4, IPAddress::toIPv6);
	}
}
	
static void convertAndBack(IPAddress addr, UnaryOperator<IPAddress> converter,
		UnaryOperator<IPAddress> converterBack) {
	System.out.println("\nstarting with " + addr);
	addr = converter.apply(addr);
	if(addr != null) {
		System.out.println("converted to " + addr);
		addr = converterBack.apply(addr);
		if(addr != null) {
			System.out.println("converted back to " + addr);
		} else {
			System.out.println("not convertible back");
		}
	} else {
		System.out.println("not convertible");
	}
}

Output:

Using standard IPv4-mapped conversion

starting with 1.2.3.4
converted to ::ffff:102:304
converted back to 1.2.3.4

starting with ::ffff:a:b
converted to 0.10.0.11
converted back to ::ffff:a:b

starting with a:b:c:d:e:f:a:b
not convertible

starting with ::1
not convertible

starting with 2002:c000:204::
not convertible

starting with 2001:0:4136:e378:8000:63bf:3fff:fdd2
not convertible

starting with fe80::c000:28e
not convertible

starting with ::ffff:0:c000:204
not convertible

starting with fe80::5efe:c000:28f
not convertible


Using customized conversion

starting with MyIPv4Address 1.2.3.4
converted to IPv4-mapped MyIPv6Address ::ffff:102:304
converted back to MyIPv4Address 1.2.3.4

starting with IPv4-mapped MyIPv6Address ::ffff:a:b
converted to MyIPv4Address 0.10.0.11
converted back to IPv4-mapped MyIPv6Address ::ffff:a:b

starting with MyIPv6Address a:b:c:d:e:f:a:b
not convertible

starting with loopback MyIPv6Address ::1
converted to MyIPv4Address 127.0.0.1
converted back to loopback MyIPv6Address ::1

starting with 6 To 4 MyIPv6Address 2002:c000:204::
converted to MyIPv4Address 192.0.2.4
converted back to 6 To 4 MyIPv6Address 2002:c000:204::

starting with Teredo MyIPv6Address 2001:0:4136:e378:8000:63bf:3fff:fdd2
converted to MyIPv4Address 192.0.2.45
converted back to Teredo MyIPv6Address 2001:0:4136:e378:8000:63bf:3fff:fdd2

starting with 6 Over 4 MyIPv6Address fe80::c000:28e
converted to MyIPv4Address 192.0.2.142
converted back to 6 Over 4 MyIPv6Address fe80::c000:28e

starting with IPv4-translated MyIPv6Address ::ffff:0:c000:204
converted to MyIPv4Address 192.0.2.4
converted back to IPv4-translated MyIPv6Address ::ffff:0:c000:204

starting with Isatap MyIPv6Address fe80::5efe:c000:28f
converted to MyIPv4Address 192.0.2.143
converted back to Isatap MyIPv6Address fe80::5efe:c000:28f

In the output, you can see the standard conversion co-existing with the customized conversion, since it is governed by the address types, starting with either IPAddressString or MyIPAddressString. With the customized conversion, the IPv4 addresses "remember" the conversion to use to go back to their IPv6 counterparts.

Write/Read Addresses to/from Direct Byte Buffer in Native Byte Order

Addresses transmitted across a network as multi-byte integers, such as in the header of an IPv4 or IPv6 packet, are in network byte order (big endian), the IP protocol standard ordering of bytes for multi-byte integers.

IPAddress uses the same ordering for segments, byte arrays and integers, much like java.net.InetAddress.

When using a native integer on a little-endian machine, or any other address value in little-endian byte order, you can switch the byte order before writing, or after reading. Most architectures in widespread use today use little-endian, whether Intel/AMD (MacOs or Windows), ARM (supports both, but most devices using ARM are little-endian), IBM Power (was big-endian but now little), so doing the byte reversal between network and native architecture is common.

The example separates IPv4 from IPv6, writes each set to a byte buffer in native byte order, then reads the addresses back in again from each buffer.

IPAddressString ipAddrStrings[] = getAddressStrings(
	"1.2.3.4", 
	"1:2:3:4:5:6:7:8",
	"127.0.0.1",
	"::1");
ByteBuffer ipv4Bytes = writeToBuffer(filterIPv4(ipAddrStrings));
ByteBuffer ipv6Bytes = writeToBuffer(filterIPv6(ipAddrStrings));
IPAddress ipv4Addresses[] = readFromBuffer(getBufferBytes(ipv4Bytes), 
	IPv4Address.BYTE_COUNT);
IPAddress ipv6Addresses[] = readFromBuffer(getBufferBytes(ipv6Bytes), 
	IPv6Address.BYTE_COUNT);
System.out.println("IPv4: " + Arrays.toString(ipv4Addresses));
System.out.println("IPv6: " + Arrays.toString(ipv6Addresses));

static IPAddressString[] getAddressStrings(String ...addrStr) {
	return Arrays.stream(addrStr).
		map(IPAddressString::new).
		toArray(IPAddressString[]::new);
}
	
static IPv4Address[] filterIPv4(IPAddressString ipAddrStrs[]) {
	return Arrays.stream(ipAddrStrs).
		filter(IPAddressString::isIPv4).
		map(ipAddrStr -> ipAddrStr.getAddress().toIPv4()).
		toArray(IPv4Address[]::new);
}
	
static IPv6Address[] filterIPv6(IPAddressString ipAddrStrs[]) {
	return Arrays.stream(ipAddrStrs).
		filter(IPAddressString::isIPv6).
		map(ipAddrStr -> ipAddrStr.getAddress().toIPv6()).
		toArray(IPv6Address[]::new);
}
	
static ByteBuffer writeToBuffer(IPv4Address ipv4Addresses[]) {
	ByteBuffer buf = ByteBuffer.allocateDirect(
		ipv4Addresses.length * IPv4Address.BYTE_COUNT);
	buf.order(ByteOrder.nativeOrder()); // use correct order when writing ints
	for(IPv4Address addr : ipv4Addresses) {
		buf.putInt(addr.intValue());
	}
	return buf;
}

static ByteBuffer writeToBuffer(IPv6Address ipv6Addresses[]) {
	int addressByteSize = IPv6Address.BYTE_COUNT;
	ByteBuffer buf = ByteBuffer.allocateDirect(
		ipv6Addresses.length * addressByteSize);
	boolean isLittleEndian = 
		ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
	byte bytes[] = new byte[addressByteSize];
	for(IPAddress addr : ipv6Addresses) {
		if(isLittleEndian) {
			addr = addr.reverseBytes();
		}
		addr.getBytes(bytes);
		buf.put(bytes);
	}
	return buf; 
}

static IPAddress[] readFromBuffer(byte bytes[], int addressByteCount) {
	IPAddressGenerator generator = new IPAddressGenerator();
	IPAddress result[] = new IPAddress[bytes.length / addressByteCount];
	boolean isLittleEndian = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
	for(int i = 0, j = i + addressByteCount, k = 0; i < bytes.length; 
			i = j, j += addressByteCount, k++) {
		IPAddress addr = generator.from(bytes, i, j);
		if(isLittleEndian) {
			addr = addr.reverseBytes();
		}
		result[k] = addr;
	}
	return result;
}
	
static byte[] getBufferBytes(ByteBuffer buffer) {
	buffer.rewind();
	byte bytes[];
	if(buffer.hasArray()) {
		bytes = buffer.array();
	} else {
		bytes = new byte[buffer.remaining()];
		buffer.get(bytes);
	}
	return bytes;
}

The output shows the addresses read back from the two buffers, which matches the original four addresses.

Output:

IPv4: [1.2.3.4, 127.0.0.1]
IPv6: [1:2:3:4:5:6:7:8, ::1]