diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java
index 8be3ded49..cc33854cc 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java
@@ -65,6 +65,8 @@ import java.util.concurrent.TimeUnit;
 
 @Getter @Setter
 public class PlayerEntity extends LivingEntity {
+    public static final float SNEAKING_POSE_HEIGHT = 1.5f;
+
     private GameProfile profile;
     private String username;
     private boolean playerList = true;  // Player is in the player list
@@ -381,7 +383,7 @@ public class PlayerEntity extends LivingEntity {
     protected void setDimensions(Pose pose) {
         float height;
         switch (pose) {
-            case SNEAKING -> height = 1.5f;
+            case SNEAKING -> height = SNEAKING_POSE_HEIGHT;
             case FALL_FLYING, SPIN_ATTACK, SWIMMING -> height = 0.6f;
             default -> {
                 super.setDimensions(pose);
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
index b61110e6c..9b1fb8263 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
@@ -28,21 +28,17 @@ package org.geysermc.geyser.entity.type.player;
 import com.github.steveice10.mc.auth.data.GameProfile;
 import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
 import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType;
-import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
 import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
 import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
 import com.nukkitx.math.vector.Vector3f;
 import com.nukkitx.protocol.bedrock.data.AttributeData;
 import com.nukkitx.protocol.bedrock.data.entity.EntityData;
 import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
-import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
-import com.nukkitx.protocol.bedrock.packet.RespawnPacket;
 import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 import lombok.Getter;
 import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
 import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.text.GeyserLocale;
 import org.geysermc.geyser.util.AttributeUtils;
 
 import java.util.Collections;
@@ -112,12 +108,7 @@ public class SessionPlayerEntity extends PlayerEntity {
     @Override
     public void setFlags(ByteEntityMetadata entityMetadata) {
         super.setFlags(entityMetadata);
-        // Swimming/crawling is controlled by the Java server
-        boolean swimming = (entityMetadata.getPrimitiveValue() & 0x10) == 0x10;
-        if (swimming) {
-            setPose(Pose.SWIMMING);
-        }
-        session.setSwimmingInWater(swimming && getFlag(EntityFlag.SPRINTING));
+        session.setSwimmingInWater((entityMetadata.getPrimitiveValue() & 0x10) == 0x10 && getFlag(EntityFlag.SPRINTING));
         refreshSpeed = true;
     }
 
diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java
index 5bd72182f..7a204ba4e 100644
--- a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java
+++ b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java
@@ -362,7 +362,22 @@ public class CollisionManager {
      * @return true if the block located at the player's floor position plus 1 would intersect with the player,
      * were they not sneaking
      */
-    public boolean isUnderSlab() {
+    public boolean mustPlayerSneakHere() {
+        return checkPose(EntityDefinitions.PLAYER.height());
+    }
+
+    /**
+     * @return true if the block located at the player's floor position plus 1 would intersect with the player,
+     * were they not crawling
+     */
+    public boolean mustPlayerCrawlHere() {
+        return checkPose(PlayerEntity.SNEAKING_POSE_HEIGHT);
+    }
+
+    /**
+     * @param height check and see if this height is invalid in the current player position
+     */
+    private boolean checkPose(float height) {
         Vector3i position = session.getPlayerEntity().getPosition().toInt();
         BlockCollision collision = BlockUtils.getCollisionAt(session, position);
         if (collision != null) {
@@ -370,7 +385,7 @@ public class CollisionManager {
             // at the current location.
             double originalY = playerBoundingBox.getMiddleY();
             double originalHeight = playerBoundingBox.getSizeY();
-            double standingY = originalY - (originalHeight / 2.0) + (EntityDefinitions.PLAYER.height() / 2.0);
+            double standingY = originalY - (originalHeight / 2.0) + (height / 2.0);
 
             playerBoundingBox.setSizeY(EntityDefinitions.PLAYER.height());
             playerBoundingBox.setMiddleY(standingY);
diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
index 02a0814d8..c7e2018dd 100644
--- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
+++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
@@ -1069,6 +1069,18 @@ public class GeyserSession implements GeyserConnection, CommandSender {
         playerEntity.setFlag(EntityFlag.SNEAKING, sneaking);
     }
 
+    public void setSwimming(boolean swimming) {
+        if (swimming) {
+            this.pose = Pose.SWIMMING;
+            playerEntity.setBoundingBoxHeight(0.6f);
+        } else {
+            this.pose = Pose.STANDING;
+            playerEntity.setBoundingBoxHeight(playerEntity.getDefinition().height());
+        }
+        playerEntity.setFlag(EntityFlag.SWIMMING, swimming);
+        playerEntity.updateBedrockMetadata();
+    }
+
     public void setFlying(boolean flying) {
         this.flying = flying;
 
@@ -1087,7 +1099,7 @@ public class GeyserSession implements GeyserConnection, CommandSender {
     public AttributeData adjustSpeed() {
         AttributeData currentPlayerSpeed = playerEntity.getAttributes().get(GeyserAttributeType.MOVEMENT_SPEED);
         if (currentPlayerSpeed != null) {
-            if ((pose.equals(Pose.SNEAKING) && !sneaking && collisionManager.isUnderSlab()) ||
+            if ((pose.equals(Pose.SNEAKING) && !sneaking && collisionManager.mustPlayerSneakHere()) ||
                     (!swimmingInWater && playerEntity.getFlag(EntityFlag.SWIMMING) && !collisionManager.isPlayerInWater())) {
                 // Either of those conditions means that Bedrock goes zoom when they shouldn't be
                 AttributeData speedAttribute = GeyserAttributeType.MOVEMENT_SPEED.getAttribute(originalSpeedAttribute / 3.32f);
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java
index f2edb6fa0..ff094461e 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAdventureSettingsTranslator.java
@@ -44,6 +44,11 @@ public class BedrockAdventureSettingsTranslator extends PacketTranslator<Adventu
             // We should always be flying in spectator mode
             session.sendAdventureSettings();
             return;
+        } else if (isFlying && session.getPlayerEntity().getFlag(EntityFlag.SWIMMING) && session.getCollisionManager().isPlayerInWater()) {
+            // As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling
+            // If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE
+            session.sendAdventureSettings();
+            return;
         }
 
         session.setFlying(isFlying);
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java
index 0fc1ee5db..0bffc3aae 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java
@@ -77,16 +77,20 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
                 session.sendUpstreamPacket(attributesPacket);
                 break;
             case START_SWIMMING:
-                if (!session.getPlayerEntity().getFlag(EntityFlag.SWIMMING)) {
+                if (!entity.getFlag(EntityFlag.SWIMMING)) {
                     ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
                     session.sendDownstreamPacket(startSwimPacket);
+
+                    session.setSwimming(true);
                 }
                 break;
             case STOP_SWIMMING:
                 // Prevent packet spam when Bedrock players are crawling near the edge of a block
-                if (session.isSwimmingInWater()) {
+                if (!session.getCollisionManager().mustPlayerCrawlHere()) {
                     ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
                     session.sendDownstreamPacket(stopSwimPacket);
+
+                    session.setSwimming(false);
                 }
                 break;
             case START_GLIDE:
@@ -135,14 +139,14 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
                 session.setSneaking(false);
                 break;
             case START_SPRINT:
-                if (!session.getPlayerEntity().getFlag(EntityFlag.SWIMMING)) {
+                if (!entity.getFlag(EntityFlag.SWIMMING)) {
                     ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
                     session.sendDownstreamPacket(startSprintPacket);
                     session.setSprinting(true);
                 }
                 break;
             case STOP_SPRINT:
-                if (!session.getPlayerEntity().getFlag(EntityFlag.SWIMMING)) {
+                if (!entity.getFlag(EntityFlag.SWIMMING)) {
                     ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
                     session.sendDownstreamPacket(stopSprintPacket);
                 }