diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
index 8597c52b2b183da972d991ee76a838af90dc7cf3..83d5d51d5e7d85c5729302a7ec9bd0637e3cb636 100644
--- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
+++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
@@ -18,6 +18,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import net.i2p.client.I2PClient;
 import net.i2p.crypto.SessionKeyManager;
@@ -86,7 +87,14 @@ class ClientConnectionRunner {
     private boolean _dead;
     /** For outbound traffic. true if i2cp.messageReliability = "none"; @since 0.8.1 */
     private boolean _dontSendMSM;
+    private final AtomicInteger _messageId; // messageId counter
     
+    // Was 32767 since the beginning (04-2004).
+    // But it's 4 bytes in the I2CP spec and stored as a long in MessageID....
+    // If this is too low and wraps around, I2CP VerifyUsage could delete the wrong message,
+    // e.g. on local access
+    private static final int MAX_MESSAGE_ID = 0x4000000;
+
     /**
      * Create a new runner against the given socket
      *
@@ -99,6 +107,7 @@ class ClientConnectionRunner {
         _messages = new ConcurrentHashMap();
         _alreadyProcessed = new ArrayList();
         _acceptedPending = new ConcurrentHashSet();
+        _messageId = new AtomicInteger(_context.random().nextInt());
     }
     
     private static volatile int __id = 0;
@@ -520,18 +529,9 @@ class ClientConnectionRunner {
         }
     }
     
-    // this *should* be mod 65536, but UnsignedInteger is still b0rked.  FIXME
-    private final static int MAX_MESSAGE_ID = 32767;
-    private static volatile int _messageId = RandomSource.getInstance().nextInt(MAX_MESSAGE_ID); // messageId counter
-    private final static Object _messageIdLock = new Object();
-    
-    static int getNextMessageId() { 
-        synchronized (_messageIdLock) {
-            int messageId = (++_messageId)%MAX_MESSAGE_ID;
-            if (_messageId >= MAX_MESSAGE_ID)
-                _messageId = 0;
-            return messageId; 
-        }
+    public int getNextMessageId() { 
+        // Don't % so we don't get negative IDs
+        return _messageId.incrementAndGet() & (MAX_MESSAGE_ID - 1);
     }
     
     /**
diff --git a/router/java/src/net/i2p/router/client/MessageReceivedJob.java b/router/java/src/net/i2p/router/client/MessageReceivedJob.java
index 88b1ca10b0ef962e754a2b3737ae92f2bd6bf196..843ebfaceacf7b276840ca95acce2cb566069866 100644
--- a/router/java/src/net/i2p/router/client/MessageReceivedJob.java
+++ b/router/java/src/net/i2p/router/client/MessageReceivedJob.java
@@ -36,7 +36,7 @@ class MessageReceivedJob extends JobImpl {
     public void runJob() {
         if (_runner.isDead()) return;
         MessageId id = new MessageId();
-        id.setMessageId(ClientConnectionRunner.getNextMessageId());
+        id.setMessageId(_runner.getNextMessageId());
         _runner.setPayload(id, _payload);
         messageAvailable(id, _payload.getSize());
     }