@Override
    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
    {
        final ExternalizableUtil util = ExternalizableUtil.getInstance();

        accessModel = AccessModel.valueOf( util.readSafeUTF( in ) );

        final long affiliatesSize = util.readLong( in );
        final List<NodeAffiliate> tmpAffiliates = new ArrayList<>();
        for ( int i = 0; i < affiliatesSize; i++ )
        {
            final JID affiliateJID = (JID) util.readSerializable( in );
            final NodeAffiliate affiliate = new NodeAffiliate( this, affiliateJID );
            if ( util.readBoolean( in ) )
            {
                affiliate.setAffiliation( NodeAffiliate.Affiliation.valueOf( util.readSafeUTF( in ) ) );
            }
            tmpAffiliates.add( affiliate );
        }
        affiliates = new CopyOnWriteArrayList<>( tmpAffiliates ); // pass them all in, in one go, to prevent iterations where arrays are copied in each iteration.

        bodyXSLT = util.readSafeUTF( in );
        util.readSerializableCollection( in, contacts, getClass().getClassLoader() );
        creationDate = (Date) util.readSerializable( in );
        creator = (JID) util.readSerializable( in );
        dataformXSLT = util.readSafeUTF( in );
        deliverPayloads = util.readBoolean( in );
        description = util.readSafeUTF( in );
        language = util.readSafeUTF( in );
        modificationDate = (Date) util.readSerializable( in );
        name = util.readSafeUTF( in );
        nodeID = util.readSafeUTF( in );
        notifyConfigChanges = util.readBoolean( in );
        notifyDelete = util.readBoolean( in );
        notifyRetract = util.readBoolean( in );
        if ( util.readBoolean( in ) )
        {
            parentIdentifier = (UniqueIdentifier) util.readSerializable( in );
        }
        else
        {
            parentIdentifier = null;
        }
        payloadType = util.readSafeUTF( in );
        presenceBasedDelivery = util.readBoolean( in );
        publisherModel = PublisherModel.valueOf( util.readSafeUTF( in ) );
        if ( util.readBoolean( in ) ) {
            replyPolicy = ItemReplyPolicy.valueOf( util.readSafeUTF( in ) );
        } else {
            replyPolicy = null;
        }
        util.readSerializableCollection( in, replyRooms, getClass().getClassLoader() );
        util.readSerializableCollection( in, replyTo, getClass().getClassLoader() );
        util.readSerializableCollection( in, rosterGroupsAllowed, getClass().getClassLoader() );
        savedToDB = util.readBoolean( in );
        serviceIdentifier = (PubSubService.UniqueIdentifier) util.readSerializable(in);
        subscriptionConfigurationRequired = util.readBoolean( in );
        subscriptionEnabled = util.readBoolean( in );

        final int subscriptionCount = util.readInt( in );
        for ( int i = 0; i < subscriptionCount; i++ )
        {
            final Node node = this;
            final JID owner = (JID) util.readSerializable( in );
            final JID jid = (JID) util.readSerializable( in );
            final NodeSubscription.State state = NodeSubscription.State.valueOf( util.readSafeUTF( in ) );
            final String id = util.readSafeUTF( in );
            final NodeSubscription subscription = new NodeSubscription( node, owner, jid, state, id );

            subscriptionsByID.put( subscription.getID(), subscription );
            subscriptionsByJID.put( subscription.getJID().toString(), subscription );
        }
    }
A unique identifier for a node, in context of all services in the system.

The properties that uniquely identify a node are its service, and its nodeId.
xmppserver/src/main/java/org/jivesoftware/openfire/pubsub/Node.java