title: How do I return to the main thread from a CompletableFuture
body: <p>I need to return to the main thread from a <code>CompletableFuture&lt;Void&gt;</code>. </p>
  
  <p>I have a web of <code>CompletableFuture</code>'s which run async to process data received and inserted into an SQL database. Once all that is complete however, I want to return to the main thread and execute a function, I have found no reasonable way of doing this.</p>
  
  <p>Calling <code>join()</code> or <code>get()</code> only blocks the thread that the <code>CompletableFuture</code> is running in... I dont want to have to continuously poll <code>isDone()</code> either.</p>
  
  <p>I have tried to use <code>whenComplete()</code>, but it still doesn't seem to work as expected...</p>
  
  <pre><code>public static void updatePermIconCache() {
      Logger.debug("thread1: " + Thread.currentThread().getId());
      hubDatabase.getLobbies().thenAccept(s -&gt; {
          for (String lobby : s) {
              for (int i = 0; i &lt; 27; i++) {
                  final int x = i;
                  hubDatabase.isInPermTable(lobby, i).thenAccept(b -&gt; {
                      if (!b) return;
                      hubDatabase.cachePermToJoinTable(lobby, x);
                  });
              }
          }
      }).whenComplete((v, t) -&gt; registerPanels());
  }
  </code></pre>
  
  <p>I want my <code>registerPanels()</code> function to be the called in the main thread, but considering even the function <code>updatePermIconCache()</code> is called async, along with other functions that determine whether this function gets called or not, I dont understand how to, well, get it back to the main thread.</p>
  
  <p>EDIT:
  Changed code to this...</p>
  
  <pre><code>public static void updatePermIconCache() {
      Logger.debug("thread1: " + Thread.currentThread().getId());
      hubDatabase.getLobbies().thenAccept(s -&gt; {
          for (String lobby : s) {
              for (int i = 0; i &lt; 27; i++) {
                  final int x = i;
                  hubDatabase.isInPermTable(lobby, i).thenAccept(b -&gt; {
                      if (!b) return;
                      hubDatabase.cachePermToJoinTable(lobby, x).join();
                      registerPanels();
                  });
              }
          }
      });
  }
  </code></pre>
  
  <p>Problem is... main thread id is <code>21</code>, in the first line of the function <code>registerPanels()</code> I print the <code>Thread#currentThread#getId()</code> and it prints multiple different numbers, ranging between <code>30</code> and <code>42</code>. It should print <code>21</code> if it is ran on the main thread, right? </p>
  
  <p>EDIT:
  This is the first method that gets called that runs async</p>
  
  <pre><code>public CompletableFuture&lt;Void&gt; calibrate() {
      return CompletableFuture.runAsync(() -&gt; {
          getLobbies().thenAccept(a -&gt; Arrays.stream(a).forEach(this::createPermToJoinTable));
          getLobbies().thenAccept(a -&gt; Arrays.stream(a).forEach(this::createVanishedTable));
      });
  }
  
  public CompletableFuture&lt;String[]&gt; getLobbies() {
      String sql = "SELECT * FROM lobby";
      List&lt;String&gt; list = new ArrayList&lt;&gt;();
      return queryAsync(r -&gt; {
          while(r.next()) {
              list.add(r.getString(1));
          }
          return r;
      }, sql).thenApplyAsync(v -&gt; list.toArray(new String[list.size()]));
  }
  </code></pre>
  
  <p>These methods are called initially from here...</p>
  
  <pre><code>public static CompletableFuture&lt;Void&gt; addToPerm(String lobby) {
      HubDatabase hubDatabase = MinelightHub.getHubDatabase();
      return hubDatabase.calibrate().thenRunAsync(() -&gt; {
          hubDatabase.addToPermToJoinTable(lobby, 0, "Admin", getDesc(true), Material.WOOL, getData(DyeColor.RED), Permissions.ADMIN, true);
          hubDatabase.addToPermToJoinTable(lobby, 1, "Mod", getDesc(false), Material.WOOL, getData(DyeColor.ORANGE), Permissions.MOD, false);
          hubDatabase.addToPermToJoinTable(lobby, 2, "Builder", getDesc(false), Material.WOOL, getData(DyeColor.CYAN), Permissions.BUILDER, false);
          hubDatabase.addToPermToJoinTable(lobby, 3, "YouTube", getDesc(false), Material.WOOL, getData(DyeColor.RED), Permissions.YOUTUBE, false);
          hubDatabase.addToPermToJoinTable(lobby, 9, "JrAdmin", getDesc(false), Material.WOOL, getData(DyeColor.RED), Permissions.JRADMIN, false);
          hubDatabase.addToPermToJoinTable(lobby, 10, "JrMod", getDesc(false), Material.WOOL, getData(DyeColor.ORANGE), Permissions.JRMOD, false);
          hubDatabase.addToPermToJoinTable(lobby, 11, "Artist", getDesc(false), Material.WOOL, getData(DyeColor.CYAN), Permissions.ARTIST, false);
          hubDatabase.addToPermToJoinTable(lobby, 12, "Twitch", getDesc(false), Material.WOOL, getData(DyeColor.PURPLE), Permissions.TWITCH, false);
          hubDatabase.addToPermToJoinTable(lobby, 17, "Default", getDesc(false), Material.WOOL, getData(DyeColor.GRAY), Permissions.DEFAULT, false);
          hubDatabase.addToPermToJoinTable(lobby, 18, "SrMod", getDesc(false), Material.WOOL, getData(DyeColor.ORANGE), Permissions.SRMOD, false);
          hubDatabase.addToPermToJoinTable(lobby, 19, "Trial", getDesc(false), Material.WOOL, getData(DyeColor.ORANGE), Permissions.TRIAL, false);
          hubDatabase.addToPermToJoinTable(lobby, 20, "Partner", getDesc(false), Material.WOOL, getData(DyeColor.BLUE), Permissions.PARTNER, false);
          hubDatabase.addToPermToJoinTable(lobby, 21, "VIP", getDesc(false), Material.WOOL, getData(DyeColor.GREEN), Permissions.VIP, false);
      }).thenRunAsync(InventoryManager::updatePermIconCache);
  }
  </code></pre>
  
  <p>Now you can kind of see the path that is necessary to help inform on this situation. 
  When I create a 'lobby' the <code>#addToPerm(String lobby)</code> method gets run
  Initially though, the tables in the SQL database need to be created before I can change add any data to it... This is what the (in the <code>#addToPerm(String lobby)</code> method) the <code>#calibrate</code> method does. Once it has created the lobbies I need to populate it with data, as such <code>hubDatabase#addToPermJoinTable()</code>. Once all that data is added, I then need to update my local cache, <code>InventoryManager#updatePermIconCache()</code>. Once all that cache is loaded, I then need to return to the main thread and call the <code>#registerPanels()</code> method as that method utilises a thread unsafe API. I am still stuck on how to do this... </p>