BUG: DALFormatError: Incorrect number of elements in array
[Edit:] - while the code snippet below doesn't produce the traceback any more, removing the skips in the narrative docs (reverting https://github.com/astropy/pyvo/commit/b3f4dc0f8446d12ca433a6d55e4563791df98f69) would still produces the error.

This error now pops up at two different places for the pyvo test suite, and we also see it downstream in astroquery. I'm fairly certain that this is pyvo or astropy related rather than upstream server issues as the two failing examples are using two different providers, one is gavo the other is cadc.

```
from pyvo.dal.sia2 import search, SIAService
CADC_SIA_URL = 'https://ws.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/sia'
results = search(CADC_SIA_URL, pos=(2.8425, 74.4846, 10), maxrec=55)

for i in results.iter_datalinks():
    print(i)
    break
```

```
======================================================= FAILURES =======================================================
___________________________________________ TestSIACadc.test_datalink_batch ____________________________________________

self = {'ID': ['ivo://cadc.nrc.ca/CFHT?949070/949070s', 'ivo://cadc.nrc.ca/CFHT?950677/950677og', 'ivo://cadc.nrc.ca/CFHT?949...0161-s/tess2019289112926-s0017-3-3-0161-s-CALIBRATED'], 'RESPONSEFORMAT': 'application/x-votable+xml;content=datalink'}
post = True

    def execute_votable(self, post=False):
        """
        Submit the query and return the results as an AstroPy votable instance.
        As this is the level where qualified error messages are available,
        they are raised here instead of in the underlying execute_stream.
    
        Returns
        -------
        astropy.io.votable.tree.Table
           an Astropy votable Table instance
    
        Raises
        ------
        DALServiceError
           for errors connecting to or communicating with the service
        DALFormatError
           for errors parsing the VOTable response
    
        See Also
        --------
        astropy.io.votable
        DALServiceError
        DALFormatError
        DALQueryError
        """
        try:
>           return votableparse(self.execute_stream(post=post).read)

pyvo/dal/query.py:243: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (functools.partial(<bound method HTTPResponse.read of <urllib3.response.HTTPResponse object at 0x123e11600>>, decode_content=True),)
kwargs = {}, i = 0, msg = '"pedantic" was deprecated in version 5.0 and will be removed in a future version. '

    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        for i in range(n):
            msg = message[i] or (f'"{old_name[i]}" was deprecated in '
                                 f'version {since[i]} and will be removed '
                                 'in a future version. ')
            # The only way to have oldkeyword inside the function is
            # that it is passed as kwarg because the oldkeyword
            # parameter was renamed to newkeyword.
            if old_name[i] in kwargs:
                value = kwargs.pop(old_name[i])
                # Display the deprecation warning only when it's not
                # pending.
                if not pending[i]:
                    if not message[i]:
                        if new_name[i] is not None:
                            msg += f'Use argument "{new_name[i]}" instead.'
                        elif alternative:
                            msg += f'\n        Use {alternative} instead.'
                    warnings.warn(msg, warning_type, stacklevel=2)
    
                # Check if the newkeyword was given as well.
                newarg_in_args = (position[i] is not None and
                                  len(args) > position[i])
                newarg_in_kwargs = new_name[i] in kwargs
    
                if newarg_in_args or newarg_in_kwargs:
                    if not pending[i]:
                        # If both are given print a Warning if relax is
                        # True or raise an Exception is relax is False.
                        if relax[i]:
                            warnings.warn(
                                f'"{old_name[i]}" and "{new_name[i]}" '
                                'keywords were set. '
                                f'Using the value of "{new_name[i]}".',
                                AstropyUserWarning)
                        else:
                            raise TypeError(
                                f'cannot specify both "{old_name[i]}" and '
                                f'"{new_name[i]}".')
                else:
                    # Pass the value of the old argument with the
                    # name of the new argument to the function
                    if new_name[i] is not None:
                        kwargs[new_name[i]] = value
                    # If old argument has no replacement, cast it back.
                    # https://github.com/astropy/astropy/issues/9914
                    else:
                        kwargs[old_name[i]] = value
    
            # Deprecated keyword without replacement is given as
            # positional argument.
            elif (not pending[i] and not new_name[i] and position[i] and
                  len(args) > position[i]):
                if alternative and not message[i]:
                    msg += f'\n        Use {alternative} instead.'
                warnings.warn(msg, warning_type, stacklevel=2)
    
>       return function(*args, **kwargs)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/utils/decorators.py:545: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

source = functools.partial(<bound method HTTPResponse.read of <urllib3.response.HTTPResponse object at 0x123e11600>>, decode_content=True)
columns = None, invalid = 'exception', verify = 'ignore', chunk_size = 256, table_number = None, table_id = None
filename = None, unit_format = None, datatype_mapping = {}, _debug_python_based_parser = False

    @deprecated_renamed_argument('pedantic', 'verify', since='5.0')
    def parse(source, columns=None, invalid='exception', verify=None,
              chunk_size=tree.DEFAULT_CHUNK_SIZE, table_number=None,
              table_id=None, filename=None, unit_format=None,
              datatype_mapping=None, _debug_python_based_parser=False):
        """
        Parses a VOTABLE_ xml file (or file-like object), and returns a
        `~astropy.io.votable.tree.VOTableFile` object.
    
        Parameters
        ----------
        source : path-like or file-like
            Path or file-like object containing a VOTABLE_ xml file.
            If file, must be readable.
    
        columns : sequence of str, optional
            List of field names to include in the output.  The default is
            to include all fields.
    
        invalid : str, optional
            One of the following values:
    
                - 'exception': throw an exception when an invalid value is
                  encountered (default)
    
                - 'mask': mask out invalid values
    
        verify : {'ignore', 'warn', 'exception'}, optional
            When ``'exception'``, raise an error when the file violates the spec,
            otherwise either issue a warning (``'warn'``) or silently continue
            (``'ignore'``). Warnings may be controlled using the standard Python
            mechanisms.  See the `warnings` module in the Python standard library
            for more information. When not provided, uses the configuration setting
            ``astropy.io.votable.verify``, which defaults to 'ignore'.
    
            .. versionchanged:: 4.0
               ``verify`` replaces the ``pedantic`` argument, which will be
               deprecated in future.
            .. versionchanged:: 5.0
                The ``pedantic`` argument is deprecated.
    
        chunk_size : int, optional
            The number of rows to read before converting to an array.
            Higher numbers are likely to be faster, but will consume more
            memory.
    
        table_number : int, optional
            The number of table in the file to read in.  If `None`, all
            tables will be read.  If a number, 0 refers to the first table
            in the file, and only that numbered table will be parsed and
            read in.  Should not be used with ``table_id``.
    
        table_id : str, optional
            The ID of the table in the file to read in.  Should not be
            used with ``table_number``.
    
        filename : str, optional
            A filename, URL or other identifier to use in error messages.
            If *filename* is None and *source* is a string (i.e. a path),
            then *source* will be used as a filename for error messages.
            Therefore, *filename* is only required when source is a
            file-like object.
    
        unit_format : str, astropy.units.format.Base instance or None, optional
            The unit format to use when parsing unit attributes.  If a
            string, must be the name of a unit formatter. The built-in
            formats include ``generic``, ``fits``, ``cds``, and
            ``vounit``.  A custom formatter may be provided by passing a
            `~astropy.units.UnitBase` instance.  If `None` (default),
            the unit format to use will be the one specified by the
            VOTable specification (which is ``cds`` up to version 1.3 of
            VOTable, and ``vounit`` in more recent versions of the spec).
    
        datatype_mapping : dict, optional
            A mapping of datatype names (`str`) to valid VOTable datatype names
            (str). For example, if the file being read contains the datatype
            "unsignedInt" (an invalid datatype in VOTable), include the mapping
            ``{"unsignedInt": "long"}``.
    
        Returns
        -------
        votable : `~astropy.io.votable.tree.VOTableFile` object
    
        See Also
        --------
        astropy.io.votable.exceptions : The exceptions this function may raise.
        """
        from . import conf
    
        invalid = invalid.lower()
        if invalid not in ('exception', 'mask'):
            raise ValueError("accepted values of ``invalid`` are: "
                             "``'exception'`` or ``'mask'``.")
    
        if verify is None:
    
            conf_verify_lowercase = conf.verify.lower()
    
            # We need to allow verify to be booleans as strings since the
            # configuration framework doesn't make it easy/possible to have mixed
            # types.
            if conf_verify_lowercase in ['false', 'true']:
                verify = conf_verify_lowercase == 'true'
            else:
                verify = conf_verify_lowercase
    
        if isinstance(verify, bool):
            verify = 'exception' if verify else 'warn'
        elif verify not in VERIFY_OPTIONS:
            raise ValueError(f"verify should be one of {'/'.join(VERIFY_OPTIONS)}")
    
        if datatype_mapping is None:
            datatype_mapping = {}
    
        config = {
            'columns': columns,
            'invalid': invalid,
            'verify': verify,
            'chunk_size': chunk_size,
            'table_number': table_number,
            'filename': filename,
            'unit_format': unit_format,
            'datatype_mapping': datatype_mapping
        }
    
        if filename is None and isinstance(source, str):
            config['filename'] = source
    
        with iterparser.get_xml_iterator(
                source,
                _debug_python_based_parser=_debug_python_based_parser) as iterator:
            return tree.VOTableFile(
>               config=config, pos=(1, 1)).parse(iterator, config)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/table.py:160: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <VOTABLE>... 1 tables ...</VOTABLE>, iterator = <astropy.utils.xml._iterparser.IterParser object at 0x123da5ee0>
config = {'_current_table_number': 1, 'chunk_size': 256, 'columns': None, 'datatype_mapping': {}, ...}

    def parse(self, iterator, config):
        config['_current_table_number'] = 0
    
        for start, tag, data, pos in iterator:
            if start:
                if tag == 'xml':
                    pass
                elif tag == 'VOTABLE':
                    if 'version' not in data:
                        warn_or_raise(W20, W20, self.version, config, pos)
                        config['version'] = self.version
                    else:
                        config['version'] = self._version = data['version']
                        if config['version'].lower().startswith('v'):
                            warn_or_raise(
                                W29, W29, config['version'], config, pos)
                            self._version = config['version'] = \
                                            config['version'][1:]
                        if config['version'] not in self._version_namespace_map:
                            vo_warn(W21, config['version'], config, pos)
    
                    if 'xmlns' in data:
                        ns_info = self._version_namespace_map.get(config['version'], {})
                        correct_ns = ns_info.get('namespace_uri')
                        if data['xmlns'] != correct_ns:
                            vo_warn(W41, (correct_ns, data['xmlns']), config, pos)
                    else:
                        vo_warn(W42, (), config, pos)
    
                    break
                else:
                    vo_raise(E19, (), config, pos)
        config.update(self._get_version_checks())
    
        tag_mapping = {
            'PARAM': self._add_param,
            'RESOURCE': self._add_resource,
            'COOSYS': self._add_coosys,
            'TIMESYS': self._add_timesys,
            'INFO': self._add_info,
            'DEFINITIONS': self._add_definitions,
            'DESCRIPTION': self._ignore_add,
            'GROUP': self._add_group}
    
        for start, tag, data, pos in iterator:
            if start:
>               tag_mapping.get(tag, self._add_unknown_tag)(

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/tree.py:3596: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <VOTABLE>... 1 tables ...</VOTABLE>, iterator = <astropy.utils.xml._iterparser.IterParser object at 0x123da5ee0>
tag = 'RESOURCE', data = {'ID': 'soda-4f70ffe7-506c-43d2-9f1e-30dda6cf4e89', 'type': 'meta', 'utype': 'adhoc:service'}
config = {'_current_table_number': 1, 'chunk_size': 256, 'columns': None, 'datatype_mapping': {}, ...}, pos = (1650, 2)

    def _add_resource(self, iterator, tag, data, config, pos):
        resource = Resource(config=config, pos=pos, **data)
        self.resources.append(resource)
>       resource.parse(self, iterator, config)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/tree.py:3475: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = </>, votable = <VOTABLE>... 1 tables ...</VOTABLE>
iterator = <astropy.utils.xml._iterparser.IterParser object at 0x123da5ee0>
config = {'_current_table_number': 1, 'chunk_size': 256, 'columns': None, 'datatype_mapping': {}, ...}

    def parse(self, votable, iterator, config):
        self._votable = votable
    
        tag_mapping = {
            'TABLE': self._add_table,
            'INFO': self._add_info,
            'PARAM': self._add_param,
            'GROUP': self._add_group,
            'COOSYS': self._add_coosys,
            'TIMESYS': self._add_timesys,
            'RESOURCE': self._add_resource,
            'LINK': self._add_link,
            'DESCRIPTION': self._ignore_add
            }
    
        for start, tag, data, pos in iterator:
            if start:
>               tag_mapping.get(tag, self._add_unknown_tag)(

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/tree.py:3285: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = </>, iterator = <astropy.utils.xml._iterparser.IterParser object at 0x123da5ee0>, tag = 'GROUP'
data = {'name': 'inputParams'}
config = {'_current_table_number': 1, 'chunk_size': 256, 'columns': None, 'datatype_mapping': {}, ...}, pos = (1654, 4)

    def _add_group(self, iterator, tag, data, config, pos):
        group = Group(self, config=config, pos=pos, **data)
        self.groups.append(group)
>       group.parse(iterator, config)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/tree.py:3241: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GROUP>... 5 entries ...</GROUP>, iterator = <astropy.utils.xml._iterparser.IterParser object at 0x123da5ee0>
config = {'_current_table_number': 1, 'chunk_size': 256, 'columns': None, 'datatype_mapping': {}, ...}

    def parse(self, iterator, config):
        tag_mapping = {
            'FIELDref': self._add_fieldref,
            'PARAMref': self._add_paramref,
            'PARAM': self._add_param,
            'GROUP': self._add_group,
            'DESCRIPTION': self._ignore_add}
    
        for start, tag, data, pos in iterator:
            if start:
>               tag_mapping.get(tag, self._add_unknown_tag)(
                    iterator, tag, data, config, pos)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/tree.py:2086: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <GROUP>... 5 entries ...</GROUP>, iterator = <astropy.utils.xml._iterparser.IterParser object at 0x123da5ee0>
tag = 'PARAM', data = {'arraysize': '2', 'datatype': 'double', 'name': 'BAND', 'ucd': 'em.wl;stat.interval', ...}
config = {'_current_table_number': 1, 'chunk_size': 256, 'columns': None, 'datatype_mapping': {}, ...}, pos = (1667, 6)

    def _add_param(self, iterator, tag, data, config, pos):
        if isinstance(self._table, VOTableFile):
            votable = self._table
        else:
            votable = self._table._votable
        param = Param(votable, config=config, pos=pos, **data)
        self.entries.append(param)
>       param.parse(iterator, config)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/tree.py:2069: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <PARAM ID="BAND" arraysize="2" datatype="double" name="BAND" ucd="em.wl;stat.interval" unit="m" value="[0. 0.]" xtype="interval"/>
iterator = <astropy.utils.xml._iterparser.IterParser object at 0x123da5ee0>
config = {'_current_table_number': 1, 'chunk_size': 256, 'columns': None, 'datatype_mapping': {}, ...}

    def parse(self, iterator, config):
        for start, tag, data, pos in iterator:
            if start:
                if tag == 'VALUES':
                    self.values.__init__(
                        self._votable, self, config=config, pos=pos, **data)
>                   self.values.parse(iterator, config)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/tree.py:1480: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = , iterator = <astropy.utils.xml._iterparser.IterParser object at 0x123da5ee0>
config = {'_current_table_number': 1, 'chunk_size': 256, 'columns': None, 'datatype_mapping': {}, ...}

    def parse(self, iterator, config):
        if self.ref is not None:
            for start, tag, data, pos in iterator:
                if start:
                    warn_or_raise(W44, W44, tag, config, pos)
                else:
                    if tag != 'VALUES':
                        warn_or_raise(W44, W44, tag, config, pos)
                    break
        else:
            for start, tag, data, pos in iterator:
                if start:
                    if tag == 'MIN':
                        if 'value' not in data:
                            vo_raise(E09, 'MIN', config, pos)
>                       self.min = data['value']

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/tree.py:1024: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = , min = '9.099660043187688E-4'

    @min.setter
    def min(self, min):
        if hasattr(self._field, 'converter') and min is not None:
>           self._min = self._field.converter.parse(min)[0]

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/tree.py:938: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <astropy.io.votable.converters.NumericArray object at 0x12403a590>, value = '9.099660043187688E-4'
config = {'_warning_counts': {<class 'astropy.io.votable.exceptions.E02'>: 1}}, pos = None

    def parse(self, value, config=None, pos=None):
        if config is None:
            config = {}
        elif config['version_1_3_or_later'] and value == '':
            return np.zeros(self._arraysize, dtype=self._base.format), True
        parts = self._splitter(value, config, pos)
        if len(parts) != self._items:
>           warn_or_raise(E02, E02, (self._items, len(parts)), config, pos)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/converters.py:607: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

warning_class = <class 'astropy.io.votable.exceptions.E02'>
exception_class = <class 'astropy.io.votable.exceptions.E02'>, args = (2, 1)
config = {'_warning_counts': {<class 'astropy.io.votable.exceptions.E02'>: 1}}, pos = None, stacklevel = 1

    def warn_or_raise(warning_class, exception_class=None, args=(), config=None,
                      pos=None, stacklevel=1):
        """
        Warn or raise an exception, depending on the verify setting.
        """
        if config is None:
            config = {}
        # NOTE: the default here is deliberately warn rather than ignore, since
        # one would expect that calling warn_or_raise without config should not
        # silence the warnings.
        config_value = config.get('verify', 'warn')
        if config_value == 'exception':
            if exception_class is None:
                exception_class = warning_class
            vo_raise(exception_class, args, config, pos)
        elif config_value == 'warn':
>           vo_warn(warning_class, args, config, pos, stacklevel=stacklevel+1)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/exceptions.py:102: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

warning_class = <class 'astropy.io.votable.exceptions.E02'>, args = (2, 1)
config = {'_warning_counts': {<class 'astropy.io.votable.exceptions.E02'>: 1}}, pos = None, stacklevel = 2

    def vo_warn(warning_class, args=(), config=None, pos=None, stacklevel=1):
        """
        Warn, with proper position information if available.
        """
        if config is None:
            config = {}
        # NOTE: the default here is deliberately warn rather than ignore, since
        # one would expect that calling warn_or_raise without config should not
        # silence the warnings.
        if config.get('verify', 'warn') != 'ignore':
            warning = warning_class(args, config, pos)
>           _suppressed_warning(warning, config, stacklevel=stacklevel+1)

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/exceptions.py:143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

warning = E02('?:?:?: E02: Incorrect number of elements in array. Expected multiple of 2, got 1')
config = {'_warning_counts': {<class 'astropy.io.votable.exceptions.E02'>: 1}}, stacklevel = 3

    def _suppressed_warning(warning, config, stacklevel=2):
        warning_class = type(warning)
        config.setdefault('_warning_counts', dict()).setdefault(warning_class, 0)
        config['_warning_counts'][warning_class] += 1
        message_count = config['_warning_counts'][warning_class]
        if message_count <= conf.max_warnings:
            if message_count == conf.max_warnings:
                warning.formatted_message += \
                    ' (suppressing further warnings of this type...)'
>           warn(warning, stacklevel=stacklevel+1)
E           astropy.io.votable.exceptions.E02: ?:?:?: E02: Incorrect number of elements in array. Expected multiple of 2, got 1

../../../.pyenv/versions/3.10.0/lib/python3.10/site-packages/astropy/io/votable/exceptions.py:83: E02

During handling of the above exception, another exception occurred:

self = <pyvo.dal.tests.test_sia2_remote.TestSIACadc object at 0x1237f0eb0>

    @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning")
    def test_datalink_batch(self):
        # Maximum batch size in CADC SIA is around 25
        # Test whether multiple batches can be retrieved
        results = search(CADC_SIA_URL, pos=(2.8425, 74.4846, 10), maxrec=55)
        ids = []
>       for i in results.iter_datalinks():

pyvo/dal/tests/test_sia2_remote.py:34: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
pyvo/dal/adhoc.py:212: in iter_datalinks
    current_batch = self.query.execute(post=True)
pyvo/dal/adhoc.py:443: in execute
    return DatalinkResults(self.execute_votable(post),
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = {'ID': ['ivo://cadc.nrc.ca/CFHT?949070/949070s', 'ivo://cadc.nrc.ca/CFHT?950677/950677og', 'ivo://cadc.nrc.ca/CFHT?949...0161-s/tess2019289112926-s0017-3-3-0161-s-CALIBRATED'], 'RESPONSEFORMAT': 'application/x-votable+xml;content=datalink'}
post = True

    def execute_votable(self, post=False):
        """
        Submit the query and return the results as an AstroPy votable instance.
        As this is the level where qualified error messages are available,
        they are raised here instead of in the underlying execute_stream.
    
        Returns
        -------
        astropy.io.votable.tree.Table
           an Astropy votable Table instance
    
        Raises
        ------
        DALServiceError
           for errors connecting to or communicating with the service
        DALFormatError
           for errors parsing the VOTable response
    
        See Also
        --------
        astropy.io.votable
        DALServiceError
        DALFormatError
        DALQueryError
        """
        try:
            return votableparse(self.execute_stream(post=post).read)
        except Exception as e:
            self.raise_if_error()
>           raise DALFormatError(e, self.queryurl)
E           pyvo.dal.exceptions.DALFormatError: E02: ?:?:?: E02: Incorrect number of elements in array. Expected multiple of 2, got 1

pyvo/dal/query.py:246: DALFormatError
```
diff --git a/CHANGES.rst b/CHANGES.rst
index 32058bc..0d623fe 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -20,6 +20,10 @@
 
 - Registry search now finds SIA v2 services. [#422]
 
+- Error messages from uws jobs are now in job.errorsummary.message
+  rather than job.message (where one wouldn't expect them given the UWS
+  schema). [#432]
+
 
 1.4 (2022-09-26)
 ================
diff --git a/pyvo/dal/tap.py b/pyvo/dal/tap.py
index 5865851..b1b2913 100644
--- a/pyvo/dal/tap.py
+++ b/pyvo/dal/tap.py
@@ -951,7 +951,10 @@ class AsyncTAPJob:
             if theres an error
         """
         if self.phase in {"ERROR", "ABORTED"}:
-            msg = self._job._message if self._job and self._job._message else ''
+            msg = ""
+            if self._job and self._job.errorsummary:
+                msg = self._job.errorsummary.message.content
+            msg = msg or "<No useful error from server>"
             raise DALQueryError("Query Error: " + msg, self.url)
 
     def fetch_result(self):
diff --git a/pyvo/dal/tests/test_tap.py b/pyvo/dal/tests/test_tap.py
index 4890fba..dda90d3 100644
--- a/pyvo/dal/tests/test_tap.py
+++ b/pyvo/dal/tests/test_tap.py
@@ -17,7 +17,7 @@ from pyvo.dal.tap import escape, search, AsyncTAPJob, TAPService
 from pyvo.dal import DALQueryError
 
 from pyvo.io.uws import JobFile
-from pyvo.io.uws.tree import Parameter, Result
+from pyvo.io.uws.tree import Parameter, Result, ErrorSummary, Message
 from pyvo.utils import prototype
 
 from astropy.time import Time, TimeDelta
@@ -180,7 +180,10 @@ class MockAsyncTAPServer:
         job.jobid = newid
         if 'test_erroneus_submit.non_existent' in request.text:
             job.phase = 'ERROR'
-            job.message = 'test_erroneus_submit.non_existent not found'
+            job._errorsummary = ErrorSummary()
+            job.errorsummary.message = Message()
+            job.errorsummary.message.content =\
+                'test_erroneus_submit.non_existent not found'
         else:
             job.phase = 'PENDING'
         job.quote = Time.now() + TimeDelta(1, format='sec')
diff --git a/pyvo/io/uws/tests/test_job.py b/pyvo/io/uws/tests/test_job.py
index daac9f9..3268ca1 100644
--- a/pyvo/io/uws/tests/test_job.py
+++ b/pyvo/io/uws/tests/test_job.py
@@ -3,6 +3,7 @@
 """
 Tests for pyvo.io.vosi
 """
+
 import pyvo.io.uws as uws
 
 from astropy.utils.data import get_pkg_data_filename
@@ -30,4 +31,4 @@ class TestJob:
 
         assert not job.errorsummary.has_detail
         assert job.errorsummary.type_ == 'fatal'
-        assert job.message == 'We have problem'
+        assert job.errorsummary.message.content == 'We have problem'
diff --git a/pyvo/io/uws/tree.py b/pyvo/io/uws/tree.py
index ad4ff6d..e918e4b 100644
--- a/pyvo/io/uws/tree.py
+++ b/pyvo/io/uws/tree.py
@@ -265,17 +265,9 @@ class JobSummary(Element):
 
     @errorsummary.adder
     def errorsummary(self, iterator, tag, data, config, pos):
-        self._errorsummary = ErrorSummary(config, pos,
-                                          'errorSummary', **data)
-
-    @uwselement(plain=True)
-    def message(self):
-        """the error message"""
-        return self._message
-
-    @message.setter
-    def message(self, message):
-        self._message = message
+        res = ErrorSummary(config, pos, 'errorSummary', **data)
+        res.parse(iterator, config)
+        self._errorsummary = res
 
 
 class Jobs(HomogeneousList, UWSElement):
['pyvo/io/uws/tests/test_job.py', 'pyvo/io/uws/tree.py', 'pyvo/dal/tests/test_tap.py', 'pyvo/dal/tap.py', 'CHANGES.rst']