diff --git a/src/pynwb/form/spec/namespace.py b/src/pynwb/form/spec/namespace.py
index b152adef..bf7388ad 100644
--- a/src/pynwb/form/spec/namespace.py
+++ b/src/pynwb/form/spec/namespace.py
@@ -337,6 +337,40 @@ class NamespaceCatalog(object):
         for subspec_dict in it:
             self.__resolve_includes(subspec_dict, catalog)
 
+    def __load_namespace(self, namespace, reader, types_key, resolve=True):
+        ns_name = namespace['name']
+        if ns_name in self.__namespaces:
+            raise KeyError("namespace '%s' already exists" % ns_name)
+        catalog = SpecCatalog()
+        included_types = dict()
+        for s in namespace['schema']:
+            if 'source' in s:
+                # read specs from file
+                dtypes = None
+                if types_key in s:
+                    dtypes = set(s[types_key])
+                self.__load_spec_file(reader, s['source'], catalog, dtypes=dtypes, resolve=resolve)
+                self.__included_sources.setdefault(ns_name, list()).append(s['source'])
+            elif 'namespace' in s:
+                # load specs from namespace
+                try:
+                    inc_ns = self.get_namespace(s['namespace'])
+                except KeyError:
+                    raise ValueError("Could not load namespace '%s'" % s['namespace'])
+                if types_key in s:
+                    types = s[types_key]
+                else:
+                    types = inc_ns.get_registered_types()
+                for ndt in types:
+                    spec = inc_ns.get_spec(ndt)
+                    spec_file = inc_ns.catalog.get_spec_source_file(ndt)
+                    catalog.register_spec(spec, spec_file)
+                included_types[s['namespace']] = tuple(types)
+        # construct namespace
+        self.add_namespace(ns_name,
+                           self.__spec_namespace_cls.build_namespace(catalog=catalog, **namespace))
+        return included_types
+
     @docval({'name': 'namespace_path', 'type': str, 'doc': 'the path to the file containing the namespaces(s) to load'},
             {'name': 'resolve',
              'type': bool,
@@ -361,35 +395,11 @@ class NamespaceCatalog(object):
             return ret
         namespaces = reader.read_namespace(namespace_path)
         types_key = self.__spec_namespace_cls.types_key()
+        for ns in namespaces:
+            if ns['name'] in self.__namespaces:
+                raise KeyError("namespace '%s' already exists" % ns['name'])
         # now load specs into namespace
         for ns in namespaces:
-            catalog = SpecCatalog()
-            included_types = dict()
-            for s in ns['schema']:
-                if 'source' in s:
-                    # read specs from file
-                    dtypes = None
-                    if types_key in s:
-                        dtypes = set(s[types_key])
-                    self.__load_spec_file(reader, s['source'], catalog, dtypes=dtypes, resolve=resolve)
-                    self.__included_sources.setdefault(ns['name'], list()).append(s['source'])
-                elif 'namespace' in s:
-                    # load specs from namespace
-                    try:
-                        inc_ns = self.get_namespace(s['namespace'])
-                    except KeyError:
-                        raise ValueError("Could not load namespace '%s'" % s['namespace'])
-                    if types_key in s:
-                        types = s[types_key]
-                    else:
-                        types = inc_ns.get_registered_types()
-                    for ndt in types:
-                        spec = inc_ns.get_spec(ndt)
-                        spec_file = inc_ns.catalog.get_spec_source_file(ndt)
-                        catalog.register_spec(spec, spec_file)
-                    included_types[s['namespace']] = tuple(types)
-            ret[ns['name']] = included_types
-            # construct namespace
-            self.add_namespace(ns['name'], self.__spec_namespace_cls.build_namespace(catalog=catalog, **ns))
+            ret[ns['name']] = self.__load_namespace(ns, reader, types_key, resolve=resolve)
         self.__included_specs[namespace_path] = ret
         return ret
diff --git a/src/pynwb/form/spec/write.py b/src/pynwb/form/spec/write.py
index 850a697b..d477c282 100644
--- a/src/pynwb/form/spec/write.py
+++ b/src/pynwb/form/spec/write.py
@@ -8,6 +8,7 @@ from abc import ABCMeta, abstractmethod
 
 from .namespace import SpecNamespace
 from .spec import GroupSpec, DatasetSpec
+from .catalog import SpecCatalog
 
 from ..utils import docval, getargs, popargs
 
@@ -59,6 +60,7 @@ class NamespaceBuilder(object):
         self.__ns_args = copy.deepcopy(kwargs)
         self.__namespaces = OrderedDict()
         self.__sources = OrderedDict()
+        self.__catalog = SpecCatalog()
         self.__dt_key = ns_cls.types_key()
 
     @docval({'name': 'source', 'type': str, 'doc': 'the path to write the spec to'},
@@ -66,6 +68,7 @@ class NamespaceBuilder(object):
     def add_spec(self, **kwargs):
         ''' Add a Spec to the namespace '''
         source, spec = getargs('source', 'spec', kwargs)
+        self.__catalog.auto_register(spec, source)
         self.add_source(source)
         self.__sources[source].setdefault(self.__dt_key, list()).append(spec)
 

NWBNamespaceBuilder does not validate spec it is building/writing.
NWBNamespaceBuilder allows construction of specs that can't be loaded due to redefining types already defined in the spec. The problem isn't discovered until load time.

Example:

    from pynwb.spec import NWBGroupSpec, NWBNamespaceBuilder

    spec1 = NWBGroupSpec("This is my new group 1",
                         "Group1",
                         neurodata_type_inc="NWBDataInterface",
                         neurodata_type_def="Group1")

    spec2 = NWBGroupSpec("This is my new group 2",
                         "Group2",
                         groups=[spec1],  # oops, this will look like it writes a valid spec, but fails on read
                         neurodata_type_inc="NWBDataInterface",
                         neurodata_type_def="Group2")

    ext_source = "myext.yaml"
    ns_builder = NWBNamespaceBuilder("Example namespace", "example")

    ns_builder.add_spec(ext_source, spec1)
    ns_builder.add_spec(ext_source, spec2)

    ns_path = "example.namespace.yaml"
    ns_builder.export(ns_path)

Fails on load:

    from pynwb import load_namespaces
    namespace_path = 'example.namespace.yaml'
    load_namespaces(namespace_path)

Tested in python 3.6 in ubuntu using latest dev commit.

- [X] Have you ensured the feature or change was not already [reported](https://github.com/NeurodataWithoutBorders/pynwb/issues/) ?
- [X] Have you included a brief and descriptive title?
- [X] Have you included a clear description of the problem you are trying to solve?
- [X] Have you included a minimal code snippet that reproduces the issue you are encountering?

diff --git a/tests/unit/pynwb_tests/test_extension.py b/tests/unit/pynwb_tests/test_extension.py
index 0e6e72ee..b425db55 100644
--- a/tests/unit/pynwb_tests/test_extension.py
+++ b/tests/unit/pynwb_tests/test_extension.py
@@ -72,3 +72,28 @@ class TestCatchDupNS(unittest.TestCase):
         load_namespaces(os.path.join(self.tempdir, self.ns_path1))
         with self.assertRaises(KeyError):
             load_namespaces(os.path.join(self.tempdir, self.ns_path2))
+
+
+class TestCatchDuplicateSpec(unittest.TestCase):
+
+    def setUp(self):
+        self.ext_source = 'fake_extension3.yaml'
+
+    def tearDown(self):
+        pass
+
+    def test_catch_duplicate_spec(self):
+        spec1 = NWBGroupSpec("This is my new group 1",
+                             "Group1",
+                             neurodata_type_inc="NWBDataInterface",
+                             neurodata_type_def="Group1")
+        spec2 = NWBGroupSpec("This is my new group 2",
+                             "Group2",
+                             groups=[spec1],
+                             neurodata_type_inc="NWBDataInterface",
+                             neurodata_type_def="Group2")
+        ns_builder = NWBNamespaceBuilder("Example namespace",
+                                         "pynwb_test_ext")
+        ns_builder.add_spec(self.ext_source, spec1)
+        with self.assertRaises(ValueError):
+            ns_builder.add_spec(self.ext_source, spec2)

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio pytest
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2018.1.18
chardet==3.0.4
coverage==6.2
execnet==1.9.0
h5py==2.7.1
idna==2.6
importlib-metadata==4.8.3
iniconfig==1.1.1
numpy==1.14.2
packaging==21.3
pluggy==1.0.0
py==1.11.0
-e git+https://github.com/NeurodataWithoutBorders/pynwb.git@0db2fc1528a7eb0204911fba88f54f07cd70bc94#egg=pynwb
pyparsing==3.1.4
pytest==7.0.1
pytest-asyncio==0.16.0
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-xdist==3.0.2
python-dateutil==2.7.2
requests==2.18.4
ruamel.yaml==0.15.37
six==1.11.0
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.22
zipp==3.6.0

name: pynwb
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - certifi==2018.1.18
      - chardet==3.0.4
      - coverage==6.2
      - execnet==1.9.0
      - h5py==2.7.1
      - idna==2.6
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - numpy==1.14.2
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-asyncio==0.16.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-xdist==3.0.2
      - python-dateutil==2.7.2
      - requests==2.18.4
      - ruamel-yaml==0.15.37
      - six==1.11.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.22
      - zipp==3.6.0
prefix: /opt/conda/envs/pynwb

tests/unit/pynwb_tests/test_extension.py::TestCatchDuplicateSpec::test_catch_duplicate_spec
tests/unit/pynwb_tests/test_extension.py::TestExtension::test_export tests/unit/pynwb_tests/test_extension.py::TestExtension::test_get_class tests/unit/pynwb_tests/test_extension.py::TestExtension::test_load_namespace tests/unit/pynwb_tests/test_extension.py::TestCatchDupNS::test_catch_dup_name
src/pynwb/form/spec/write.py src/pynwb/form/spec/namespace.py
src/pynwb/form/spec/write.py src/pynwb/form/spec/namespace.py