diff --git a/doc/RTD/source/ImplementationDetails/index.rst b/doc/RTD/source/ImplementationDetails/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..dda984293e7cce7aeffd9886be8c4a633ee03c14
--- /dev/null
+++ b/doc/RTD/source/ImplementationDetails/index.rst
@@ -0,0 +1,41 @@
+.. Implementation details
+   Loic Hausammann, 2020
+   Matthieu Schaller, 2020
+
+.. _implementation_details:
+
+Implementation details
+======================
+
+Generating new unique IDs
+-------------------------
+
+When spawning new particles (not converting them) for star formation or other
+similar processes, the code needs to create new unique particle IDs. This is
+implemented in the file ``space_unique_id.c`` and can be switched on/off in the
+star formation file ``star_formation_struct.h`` by setting the variable
+``star_formation_need_unique_id`` to 1 or 0.
+
+The generation of new IDs is done by computing the maximal ID present in the
+initial condition (across all particle types) and then attributing two batches
+of new, unused IDs to each MPI rank.  The size of each batch is computed in the
+same way as the count of extra particles in order to ensure that we will have
+enough available IDs between two tree rebuilds (where the extra particles are
+regenerated).
+
+When a new ID is requested, the next available ID in the first batch is
+returned. If the last available ID in the first batch is requested, we switch to
+the next batch of IDs and flag it for regeneration at the next rebuild time.  If
+the second batch is also fully used, the code will exit with an error message
+[#f1]_. At each tree-rebuild steps, the ranks will request a new batch if
+required and make sure the batches are unique across all MPI ranks.
+
+As we are using the maximal ID from the ICs, nothing can be done against the user
+providing the maximal integer possible as an ID (that can for instance be the
+case in some of the EAGLE ICs as the ID encode their Lagrangian position on a
+Peano-Hilbert curve). 
+
+
+.. [#f1] Thanks to the size of the fresh ID batches, the code should run out of
+	 extra particles before reaching this point and triggered a new rebuild
+	 if this is allowed by the star formation scheme.
diff --git a/doc/RTD/source/index.rst b/doc/RTD/source/index.rst
index d9faf2855ea0c50f177febfa15c981c92d325114..ae75332e7a6caa5d1116a53ce0af642d3a22f01b 100644
--- a/doc/RTD/source/index.rst
+++ b/doc/RTD/source/index.rst
@@ -33,3 +33,4 @@ difference is the parameter file that will need to be adapted for SWIFT.
    VELOCIraptorInterface/index
    AnalysisTools/index
    Logger/index
+   ImplementationDetails/index
diff --git a/examples/main.c b/examples/main.c
index 2528ce589ef7e0eca09181d5976329c9f193bf83..e197830459c6b3ba7c888c66527871b91c1d988a 100644
--- a/examples/main.c
+++ b/examples/main.c
@@ -1058,7 +1058,7 @@ int main(int argc, char *argv[]) {
     space_init(&s, params, &cosmo, dim, parts, gparts, sparts, bparts, Ngas,
                Ngpart, Nspart, Nbpart, periodic, replicate, generate_gas_in_ics,
                with_hydro, with_self_gravity, with_star_formation,
-               with_DM_background_particles, talking, dry_run);
+               with_DM_background_particles, talking, dry_run, nr_nodes);
 
     if (myrank == 0) {
       clocks_gettime(&toc);
diff --git a/src/Makefile.am b/src/Makefile.am
index cf6c71644d7161001828c22260c5b56efce9ac75..d87f0696f65f815baef4163659384a252b1355dc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -57,7 +57,8 @@ include_HEADERS = space.h runner.h queue.h task.h lock.h cell.h part.h const.h \
     pressure_floor.h pressure_floor_struct.h pressure_floor_iact.h \
     velociraptor_struct.h velociraptor_io.h random.h memuse.h mpiuse.h memuse_rnodes.h \
     black_holes.h black_holes_io.h black_holes_properties.h black_holes_struct.h \
-    feedback.h feedback_struct.h feedback_properties.h task_order.h
+    feedback.h feedback_struct.h feedback_properties.h task_order.h \
+    space_unique_id.h
 
 # source files for EAGLE cooling
 QLA_COOLING_SOURCES =
@@ -107,7 +108,7 @@ AM_SOURCES = space.c runner_main.c runner_doiact_hydro.c runner_doiact_limiter.c
     collectgroup.c hydro_space.c equation_of_state.c \
     chemistry.c cosmology.c restart.c mesh_gravity.c velociraptor_interface.c \
     outputlist.c velociraptor_dummy.c logger_io.c memuse.c mpiuse.c memuse_rnodes.c fof.c \
-    hashmap.c pressure_floor.c \
+    hashmap.c pressure_floor.c space_unique_id.c \
     $(QLA_COOLING_SOURCES) \
     $(EAGLE_COOLING_SOURCES) $(EAGLE_FEEDBACK_SOURCES) \
     $(GRACKLE_COOLING_SOURCES) $(GEAR_FEEDBACK_SOURCES)
diff --git a/src/cell.c b/src/cell.c
index 056c0aee013e1321be61a3542906b98a97809b83..0702d0aa79c8bf97923eee955ef1216b7c2472a8 100644
--- a/src/cell.c
+++ b/src/cell.c
@@ -6173,8 +6173,8 @@ struct spart *cell_spawn_new_spart_from_part(struct engine *e, struct cell *c,
   /* Copy the gpart */
   *gp = *p->gpart;
 
-  /* Assign the ID back */
-  sp->id = p->id;
+  /* Assign the ID. */
+  sp->id = space_get_new_unique_id(e->s);
   gp->type = swift_type_stars;
 
   /* Re-link things */
diff --git a/src/space.c b/src/space.c
index 2ed86a9ff1e83a8212f25cbbd1c850a7d9f94769..765d1d69decb1f59411642cd9a52cb29af32c678 100644
--- a/src/space.c
+++ b/src/space.c
@@ -60,6 +60,7 @@
 #include "proxy.h"
 #include "restart.h"
 #include "sort_part.h"
+#include "space_unique_id.h"
 #include "star_formation.h"
 #include "star_formation_logger.h"
 #include "stars.h"
@@ -1818,6 +1819,9 @@ void space_rebuild(struct space *s, int repartitioned, int verbose) {
   swift_free("b_index", b_index);
   swift_free("cell_bpart_counts", cell_bpart_counts);
 
+  /* Update the slice of unique IDs. */
+  space_update_unique_id(s);
+
 #ifdef WITH_MPI
 
   /* Re-allocate the index array for the gparts if needed.. */
@@ -4818,6 +4822,7 @@ void space_convert_quantities(struct space *s, int verbose) {
  * @param DM_background Are we running with some DM background particles?
  * @param verbose Print messages to stdout or not.
  * @param dry_run If 1, just initialise stuff, don't do anything with the parts.
+ * @param nr_nodes The number of MPI rank.
  *
  * Makes a grid of edge length > r_max and fills the particles
  * into the respective cells. Cells containing more than #space_splitsize
@@ -4830,8 +4835,8 @@ void space_init(struct space *s, struct swift_params *params,
                 struct bpart *bparts, size_t Npart, size_t Ngpart,
                 size_t Nspart, size_t Nbpart, int periodic, int replicate,
                 int generate_gas_in_ics, int hydro, int self_gravity,
-                int star_formation, int DM_background, int verbose,
-                int dry_run) {
+                int star_formation, int DM_background, int verbose, int dry_run,
+                int nr_nodes) {
 
   /* Clean-up everything */
   bzero(s, sizeof(struct space));
@@ -5111,11 +5116,17 @@ void space_init(struct space *s, struct swift_params *params,
 #endif
 
   /* Do we want any spare particles for on the fly creation? */
-  if (!star_formation || !swift_star_formation_model_creates_stars)
+  if (!star_formation || !swift_star_formation_model_creates_stars) {
     space_extra_sparts = 0;
+  }
 
   /* Build the cells recursively. */
   if (!dry_run) space_regrid(s, verbose);
+
+  /* Compute the max id for the generation of unique id. */
+  if (star_formation && swift_star_formation_model_creates_stars) {
+    space_init_unique_id(s, nr_nodes);
+  }
 }
 
 /**
@@ -5764,6 +5775,9 @@ void space_clean(struct space *s) {
   swift_free("gparts_foreign", s->gparts_foreign);
   swift_free("bparts_foreign", s->bparts_foreign);
 #endif
+
+  if (lock_destroy(&s->unique_id.lock) != 0)
+    error("Failed to destroy spinlocks.");
 }
 
 /**
diff --git a/src/space.h b/src/space.h
index acb55bd26128364c69f56dc557a5877115e30804..b7ba589482d8bd0a464cc6d3cc01076e1f4d2442 100644
--- a/src/space.h
+++ b/src/space.h
@@ -34,6 +34,7 @@
 #include "lock.h"
 #include "parser.h"
 #include "part.h"
+#include "space_unique_id.h"
 #include "velociraptor_struct.h"
 
 /* Avoid cyclic inclusions */
@@ -277,6 +278,9 @@ struct space {
   /*! The group information returned by VELOCIraptor for each #gpart. */
   struct velociraptor_gpart_data *gpart_group_data;
 
+  /*! Structure dealing with the computation of a unique ID */
+  struct unique_id unique_id;
+
 #ifdef WITH_MPI
 
   /*! Buffers for parts that we will receive from foreign cells. */
@@ -316,8 +320,8 @@ void space_init(struct space *s, struct swift_params *params,
                 struct bpart *bparts, size_t Npart, size_t Ngpart,
                 size_t Nspart, size_t Nbpart, int periodic, int replicate,
                 int generate_gas_in_ics, int hydro, int gravity,
-                int star_formation, int DM_background, int verbose,
-                int dry_run);
+                int star_formation, int DM_background, int verbose, int dry_run,
+                int nr_nodes);
 void space_sanitize(struct space *s);
 void space_map_cells_pre(struct space *s, int full,
                          void (*fun)(struct cell *c, void *data), void *data);
diff --git a/src/space_unique_id.c b/src/space_unique_id.c
new file mode 100644
index 0000000000000000000000000000000000000000..3a6f0c48fb6d092cdd8e7613b2ff27027278913f
--- /dev/null
+++ b/src/space_unique_id.c
@@ -0,0 +1,247 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2020 Loic Hausammann (loic.hausammann@epfl.ch)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Some standard headers. */
+#include <limits.h>
+
+/* MPI headers. */
+#ifdef WITH_MPI
+#include <mpi.h>
+#endif
+
+/* This object's header. */
+#include "space_unique_id.h"
+
+/* Local headers. */
+#include "engine.h"
+#include "lock.h"
+#include "space.h"
+
+/**
+ * @brief Update the unique id structure by requesting a
+ * new batch if required.
+ *
+ * @param s The #space.
+ */
+void space_update_unique_id(struct space *s) {
+  /* Do we need unique IDs? */
+  if (!star_formation_need_unique_id) {
+    return;
+  }
+
+  const int require_new_batch = s->unique_id.next_batch.current == 0;
+
+#ifdef WITH_MPI
+  const struct engine *e = s->e;
+
+  /* Check if the other ranks need a batch. */
+  int *all_requires = (int *)malloc(sizeof(int) * e->nr_nodes);
+
+  /* Do the communication */
+  MPI_Allgather(&require_new_batch, 1, MPI_INT, all_requires, 1, MPI_INT,
+                MPI_COMM_WORLD);
+
+  /* Compute the position of this rank batch and the position of
+     the next free batch. */
+  int local_index = 0;
+  int total_shift = 0;
+  for (int i = 0; i < e->nr_nodes; i++) {
+    total_shift += all_requires[i];
+    if (i < e->nodeID) {
+      local_index += all_requires[i];
+    }
+  }
+
+  /* Free the allocated resources. */
+  free(all_requires);
+
+#else
+
+  int local_index = 0;
+  int total_shift = require_new_batch;
+
+#endif  // WITH_MPI
+
+  /* Compute the size of each batch. */
+  const long long batch_size = (space_extra_parts + space_extra_sparts +
+                                space_extra_gparts + space_extra_bparts) *
+                               s->nr_cells;
+
+  /* Get a new batch. */
+  if (require_new_batch) {
+    /* First check against an overflow. */
+    const long long local_shift = local_index * batch_size;
+    if (s->unique_id.global_next_id > LLONG_MAX - (local_shift + batch_size)) {
+      error("Overflow for the unique IDs.");
+    }
+    /* Now assign it. */
+    s->unique_id.next_batch.current = s->unique_id.global_next_id + local_shift;
+    s->unique_id.next_batch.max =
+        s->unique_id.global_next_id + local_shift + batch_size;
+  }
+
+  /* Shift the position of the next available batch. */
+  const long long shift = total_shift * batch_size;
+  if (s->unique_id.global_next_id > LLONG_MAX - shift) {
+    error("Overflow for the unique IDs.");
+  }
+  s->unique_id.global_next_id += shift;
+}
+
+/**
+ * @brief Get a new unique ID.
+ *
+ * @param s the #space.
+ *
+ * @return The new unique ID
+ */
+long long space_get_new_unique_id(struct space *s) {
+  /* Do we need unique IDs? */
+  if (!star_formation_need_unique_id) {
+    error("The scheme selected does not seem to use unique ID.");
+  }
+
+  /* Get the lock. */
+  lock_lock(&s->unique_id.lock);
+
+  /* Get the current available id. */
+  const long long id = s->unique_id.current_batch.current;
+
+  /* Update the counter. */
+  s->unique_id.current_batch.current++;
+
+  /* Check if everything is fine */
+  if (s->unique_id.current_batch.current > s->unique_id.current_batch.max) {
+    error("Failed to get a new ID");
+  }
+
+  /* Check if need to move to the next batch. */
+  else if (s->unique_id.current_batch.current ==
+           s->unique_id.current_batch.max) {
+
+    /* Check if the next batch is already used */
+    if (s->unique_id.next_batch.current == 0) {
+      error("Failed to obtain a new unique ID.");
+    }
+
+    s->unique_id.current_batch = s->unique_id.next_batch;
+
+    /* Reset the next batch. */
+    s->unique_id.next_batch.current = 0;
+    s->unique_id.next_batch.max = 0;
+  }
+
+  /* Release the lock. */
+  if (lock_unlock(&s->unique_id.lock) != 0) {
+    error("Impossible to unlock the unique id.");
+  }
+
+  return id;
+}
+
+/**
+ * @brief Initialize the computation of unique IDs.
+ *
+ * @param s The #space.
+ * @param nr_nodes The number of MPI ranks.
+ */
+void space_init_unique_id(struct space *s, int nr_nodes) {
+  /* Do we need unique IDs? */
+  if (!star_formation_need_unique_id) {
+    return;
+  }
+
+  /* Set the counter to 0. */
+  s->unique_id.global_next_id = 0;
+
+  /* Check the parts for the max id. */
+  for (size_t i = 0; i < s->nr_parts; i++) {
+    s->unique_id.global_next_id =
+        max(s->unique_id.global_next_id, s->parts[i].id);
+  }
+
+  /* Check the gparts for the max id. */
+  for (size_t i = 0; i < s->nr_gparts; i++) {
+    s->unique_id.global_next_id =
+        max(s->unique_id.global_next_id, s->gparts[i].id_or_neg_offset);
+  }
+
+  /* Check the sparts for the max id. */
+  for (size_t i = 0; i < s->nr_sparts; i++) {
+    s->unique_id.global_next_id =
+        max(s->unique_id.global_next_id, s->sparts[i].id);
+  }
+
+  /* Check the bparts for the max id. */
+  for (size_t i = 0; i < s->nr_bparts; i++) {
+    s->unique_id.global_next_id =
+        max(s->unique_id.global_next_id, s->bparts[i].id);
+  }
+
+#ifdef WITH_MPI
+  /* Find the global max. */
+  MPI_Allreduce(MPI_IN_PLACE, &s->unique_id.global_next_id, 1, MPI_LONG_LONG,
+                MPI_MAX, MPI_COMM_WORLD);
+#endif  // WITH_MPI
+
+  /* Get the first unique id. */
+  if (s->unique_id.global_next_id == LLONG_MAX) {
+    error("Overflow for the unique id.");
+  }
+  s->unique_id.global_next_id++;
+
+  /* Compute the size of each batch. */
+  const long long batch_size = (space_extra_parts + space_extra_sparts +
+                                space_extra_gparts + space_extra_bparts) *
+                               s->nr_cells;
+
+  /* Compute the initial batchs (each rank has 2 batchs). */
+  if (s->unique_id.global_next_id > LLONG_MAX - 2 * engine_rank * batch_size) {
+    error("Overflow for the unique id.");
+  }
+  const long long init =
+      s->unique_id.global_next_id + 2 * engine_rank * batch_size;
+
+  /* Set the batchs and check for overflows. */
+  s->unique_id.current_batch.current = init;
+
+  if (init > LLONG_MAX - batch_size) {
+    error("Overflow for the unique id.");
+  }
+  s->unique_id.current_batch.max = init + batch_size;
+  s->unique_id.next_batch.current = s->unique_id.current_batch.max;
+
+  if (s->unique_id.next_batch.current > LLONG_MAX - batch_size) {
+    error("Overflow for the unique id.");
+  }
+  s->unique_id.next_batch.max = s->unique_id.next_batch.current + batch_size;
+
+  /* Update the next available id */
+  if (s->unique_id.global_next_id > LLONG_MAX - 2 * batch_size * nr_nodes) {
+    error("Overflow for the unique id.");
+  }
+  s->unique_id.global_next_id += 2 * batch_size * nr_nodes;
+
+  /* Initialize the lock. */
+  if (lock_init(&s->unique_id.lock) != 0)
+    error("Failed to init spinlock for the unique ids.");
+}
diff --git a/src/space_unique_id.h b/src/space_unique_id.h
new file mode 100644
index 0000000000000000000000000000000000000000..49ec49d2bdc09b46451a4decdadd6dce046d5a20
--- /dev/null
+++ b/src/space_unique_id.h
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2020 Loic Hausammann (loic.hausammann@epfl.ch)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+
+#ifndef SWIFT_SPACE_UNIQUE_ID_H
+#define SWIFT_SPACE_UNIQUE_ID_H
+
+/* Config parameters. */
+#include "../config.h"
+
+/* Local includes */
+#include "lock.h"
+
+/* Predefine the space structure */
+struct space;
+
+/**
+ * @brief Batch of unique IDs for particle creation.
+ */
+struct batch {
+  /*! Current free unique id */
+  long long current;
+
+  /*! Maximal unique id in this batch  (not included) */
+  long long max;
+};
+
+/*! Structure dealing with the computation of a unique ID */
+struct unique_id {
+  /*! Current batch of unique ids */
+  struct batch current_batch;
+
+  /*! Next batch of unique ids */
+  struct batch next_batch;
+
+  /* Global next slot available */
+  long long global_next_id;
+
+  /* Lock for the unique ids */
+  swift_lock_type lock;
+};
+
+void space_update_unique_id(struct space *s);
+long long space_get_new_unique_id(struct space *s);
+void space_init_unique_id(struct space *s, int nr_nodes);
+
+#endif  // SWIFT_SPACE_UNIQUE_ID_H
diff --git a/src/star_formation/EAGLE/star_formation_struct.h b/src/star_formation/EAGLE/star_formation_struct.h
index f715ba345392dc6efd5a11590cdea46c6930d002..26fa60ad388654f84c2dc52f4a4a497f65a6152e 100644
--- a/src/star_formation/EAGLE/star_formation_struct.h
+++ b/src/star_formation/EAGLE/star_formation_struct.h
@@ -19,6 +19,10 @@
 #ifndef SWIFT_EAGLE_STAR_FORMATION_STRUCT_H
 #define SWIFT_EAGLE_STAR_FORMATION_STRUCT_H
 
+/* Do we need unique IDs (only useful when spawning
+   new particles, conversion gas->stars does not need unique IDs) */
+#define star_formation_need_unique_id 0
+
 /**
  * @brief Star-formation-related properties stored in the extended particle
  * data.
diff --git a/src/star_formation/GEAR/star_formation_struct.h b/src/star_formation/GEAR/star_formation_struct.h
index bce8170301279f2d049890aea96e0434b2fa4944..09a077d4bf9adccbb97c86d4067deb9a41550ddc 100644
--- a/src/star_formation/GEAR/star_formation_struct.h
+++ b/src/star_formation/GEAR/star_formation_struct.h
@@ -19,6 +19,10 @@
 #ifndef SWIFT_GEAR_STAR_FORMATION_STRUCT_H
 #define SWIFT_GEAR_STAR_FORMATION_STRUCT_H
 
+/* Do we need unique IDs (only useful when spawning
+   new particles, conversion gas->stars does not need unique IDs) */
+#define star_formation_need_unique_id 1
+
 /**
  * @brief Star-formation-related properties stored in the extended particle
  * data.
diff --git a/src/star_formation/QLA/star_formation_struct.h b/src/star_formation/QLA/star_formation_struct.h
index 18d39c4471698fa54caf5bdc041402ff63fb8e37..a2cc683d999b8ebc5e60e75e5d5fb3e8b6dec732 100644
--- a/src/star_formation/QLA/star_formation_struct.h
+++ b/src/star_formation/QLA/star_formation_struct.h
@@ -19,6 +19,10 @@
 #ifndef SWIFT_QLA_STAR_FORMATION_STRUCT_H
 #define SWIFT_QLA_STAR_FORMATION_STRUCT_H
 
+/* Do we need unique IDs (only useful when spawning
+   new particles, conversion gas->stars does not need unique IDs) */
+#define star_formation_need_unique_id 1
+
 /**
  * @brief Star-formation-related properties stored in the extended particle
  * data.
diff --git a/src/star_formation/none/star_formation_struct.h b/src/star_formation/none/star_formation_struct.h
index 4b537d2a3d34fa8ce719cf194047bd7c94d5d1da..909e593bf3235391a0a53dd396c55537a972ba37 100644
--- a/src/star_formation/none/star_formation_struct.h
+++ b/src/star_formation/none/star_formation_struct.h
@@ -19,6 +19,10 @@
 #ifndef SWIFT_NONE_STAR_FORMATION_STRUCT_H
 #define SWIFT_NONE_STAR_FORMATION_STRUCT_H
 
+/* Do we need unique IDs (only useful when spawning
+   new particles, conversion gas->stars does not need unique IDs) */
+#define star_formation_need_unique_id 0
+
 /**
  * @brief Star-formation-related properties stored in the extended particle
  * data.