The Bug:
You can switch fishing rods while the line is still cast.
Steps to Reproduce:
Obtain two fishing rods and place them next to one another in your hotbar.
Use one of the fishing rods.
Switch to the other fishing rod.
Take note as to whether or not you can switch fishing rods while the line is still cast.
Observed Behavior:
You can switch fishing rods while the line is still cast.
Expected Behavior:
You would not be able to switch fishing rods while the line is still cast.
Expected Behavior:
You would not be able to switch fishing rods while the line is still cast.
Code Analysis & Fix:
Code analysis and fix by @unknown can be found in this comment.
Linked issues
is duplicated by 7
relates to 2
Attachments
Comments 36
Yay 😃 My first confirmed issue!
Not only does the line stay cast, but after being signalled that you have a fish on the line, you can switch to a different rod and reel in the fish with that one! Also, confirming in 1.6.1 .
Does it affect 1.9? Also , if one fishing rod has luck of the sea 1 and the other has lure 1 , will the fish you catch be affected by lure or luck of the sea?

Confirmed for
16w15b Also affects switching with offhand
The fishing rod with which you stop fishing gets damaged and probably affects the fished item as well
Can confirm in 21w17a.
I'd like to request ownership of this ticket since the current reporter has been inactive since October 2018. I'm willing to provide all of the necessary information and will keep this report updated.
Can confirm in 1.17.

Can confirm in 22w17a.

Code Analysis:
The following is based on a decompiled version of Minecraft 1.20.1 using MCP-Reborn.
net.minecraft.world.entity.projectile.FishingHook.java // Original
public class FishingHook extends Projectile {
  ...
  public void tick() {
    this.syncronizedRandom.setSeed(this.getUUID().getLeastSignificantBits() ^ this.level().getGameTime());
    super.tick();
    Player player = this.getPlayerOwner();
    if (player == null) {
      this.discard();
    } else if (this.level().isClientSide || !this.shouldStopFishing(player)) {
      if (this.onGround()) {
        ++this.life;
        if (this.life >= 1200) {
          this.discard();
          return;
        }
      } else {
        this.life = 0;
      }
      ...
    }
    ...
  }
  ...
}
net.minecraft.world.item.FishingRodItem.java // Original
public class FishingRodItem extends Item implements Vanishable {
  ...
  public InteractionResultHolder < ItemStack > use(Level p_41290_, Player p_41291_, InteractionHand p_41292_) {
    ItemStack itemstack = p_41291_.getItemInHand(p_41292_);
    if (p_41291_.fishing != null) {
      if (!p_41290_.isClientSide) {
        int i = p_41291_.fishing.retrieve(itemstack);
        itemstack.hurtAndBreak(i, p_41291_, (p_41288_) - > {
          p_41288_.broadcastBreakEvent(p_41292_);
        });
      }      p_41290_.playSound((Player) null, p_41291_.getX(), p_41291_.getY(), p_41291_.getZ(), SoundEvents.FISHING_BOBBER_RETRIEVE, SoundSource.NEUTRAL, 1.0 F, 0.4 F / (p_41290_.getRandom().nextFloat() * 0.4 F + 0.8 F));
      p_41291_.gameEvent(GameEvent.ITEM_INTERACT_FINISH);
    } else {
      p_41290_.playSound((Player) null, p_41291_.getX(), p_41291_.getY(), p_41291_.getZ(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.NEUTRAL, 0.5 F, 0.4 F / (p_41290_.getRandom().nextFloat() * 0.4 F + 0.8 F));
      if (!p_41290_.isClientSide) {
        int k = EnchantmentHelper.getFishingSpeedBonus(itemstack);
        int j = EnchantmentHelper.getFishingLuckBonus(itemstack);
        p_41290_.addFreshEntity(new FishingHook(p_41291_, p_41290_, j, k));
      }      p_41291_.awardStat(Stats.ITEM_USED.get(this));
      p_41291_.gameEvent(GameEvent.ITEM_INTERACT_START);
    }    return InteractionResultHolder.sidedSuccess(itemstack, p_41290_.isClientSide());
  }
  ...
}
net.minecraft.world.entity.LivingEntity.java // Original
public abstract class LivingEntity extends Entity implements Attackable {
  ...
  private void handleHandSwap(Map < EquipmentSlot, ItemStack > p_21092_) {
    ItemStack itemstack = p_21092_.get(EquipmentSlot.MAINHAND);
    ItemStack itemstack1 = p_21092_.get(EquipmentSlot.OFFHAND);
    if (itemstack != null && itemstack1 != null && ItemStack.matches(itemstack, this.getLastHandItem(EquipmentSlot.OFFHAND)) && ItemStack.matches(itemstack1, this.getLastHandItem(EquipmentSlot.MAINHAND))) {
      ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundEntityEventPacket(this, (byte) 55));
      p_21092_.remove(EquipmentSlot.MAINHAND);
      p_21092_.remove(EquipmentSlot.OFFHAND);
      this.setLastHandItem(EquipmentSlot.MAINHAND, itemstack.copy());
      this.setLastHandItem(EquipmentSlot.OFFHAND, itemstack1.copy());
    }
  }
  ...
}
These are the 3 original classes that will relate to the fix of most fishing rod swapping bugs. As I can tell, we aren't keeping track of the fishing rod being used. Therefore, we can swap out a secondary fishing rod, which then swaps the fishing line to that secondary fishing rod. My original plan was to keep track of the itemstack being used when casting the fishing rod, BUT I couldn't find a method to separate two similar itemstacks. Therefore, I had to resort to keeping track of the slot of the casted fishing rod. Â
Fix:
net.minecraft.world.entity.projectile.FishingHook.java // Updated
public class FishingHook extends Projectile {
  ...
  private int rodSlot; // -1 means offhand, -2 means spawned
  ...
  private FishingHook(EntityType << ? extends FishingHook > p_150141_, Level p_150142_, int rodSlot, int p_150143_, int p_150144_) { // added rodSlot
    super(p_150141_, p_150142_);
    this.noCulling = true;
    this.luck = Math.max(0, p_150143_);
    this.lureSpeed = Math.max(0, p_150144_);
    this.rodSlot = rodSlot; // added line
  }  public FishingHook(EntityType << ? extends FishingHook > p_150138_, Level p_150139_) {
    this(p_150138_, p_150139_, -2, 0, 0); // added parameter for rodSlot
  }  public FishingHook(Player p_37106_, Level p_37107_, int rodSlot, int p_37108_, int p_37109_) { // added rodSlot
    this(EntityType.FISHING_BOBBER, p_37107_, rodSlot, p_37108_, p_37109_); // added parameter for rodSlot
    this.setOwner(p_37106_);
    float f = p_37106_.getXRot();
    float f1 = p_37106_.getYRot();
    float f2 = Mth.cos(-f1 * ((float) Math.PI / 180 F) - (float) Math.PI);
    float f3 = Mth.sin(-f1 * ((float) Math.PI / 180 F) - (float) Math.PI);
    float f4 = -Mth.cos(-f * ((float) Math.PI / 180 F));
    float f5 = Mth.sin(-f * ((float) Math.PI / 180 F));
    double d0 = p_37106_.getX() - (double) f3 * 0.3 D;
    double d1 = p_37106_.getEyeY();
    double d2 = p_37106_.getZ() - (double) f2 * 0.3 D;
    this.moveTo(d0, d1, d2, f1, f);
    Vec3 vec3 = new Vec3((double)(-f3), (double) Mth.clamp(-(f5 / f4), -5.0 F, 5.0 F), (double)(-f2));
    double d3 = vec3.length();
    vec3 = vec3.multiply(0.6 D / d3 + this.random.triangle(0.5 D, 0.0103365 D), 0.6 D / d3 + this.random.triangle(0.5 D, 0.0103365 D), 0.6 D / d3 + this.random.triangle(0.5 D, 0.0103365 D));
    this.setDeltaMovement(vec3);
    this.setYRot((float)(Mth.atan2(vec3.x, vec3.z) * (double)(180 F / (float) Math.PI)));
    this.setXRot((float)(Mth.atan2(vec3.y, vec3.horizontalDistance()) * (double)(180 F / (float) Math.PI)));
    this.yRotO = this.getYRot();
    this.xRotO = this.getXRot();
  }
  ...
  public void tick() {
    this.syncronizedRandom.setSeed(this.getUUID().getLeastSignificantBits() ^ this.level().getGameTime());
    super.tick();
    Player player = this.getPlayerOwner();
    if (player == null) {
      this.discard();
    } else {
      Inventory inventory = player.getInventory();
      if (this.rodSlot == -1 && (inventory.getItem(inventory.selected).is(Items.FISHING_ROD)) || this.rodSlot >= 0 && (this.rodSlot != inventory.selected || !inventory.getItem(inventory.selected).is(Items.FISHING_ROD)))
        this.discard();
      else if (this.level().isClientSide || !this.shouldStopFishing(player)) {
        if (this.onGround()) {
          ++this.life;
          if (this.life >= 1200) {
            this.discard();
            return;
          }
        } else {
          this.life = 0;
        }
        ...
      }
      ...
    }
    ...
  }
  ...
}
In this modified version of FishingHook.java
, we now keep track of where the fishing rod is located in the player's inventory. Whether if it was spawned in, casted from the offhand, or casted from the main hand. Then, we simply check the slot of where the rod is if the fishing rod is still in that original slot.Â
After we've solved most of the fishing rod swapping bugs, we now have to alter other classes to make it compatible with our changes.
net.minecraft.world.item.FishingRodItem.java // Updated
public class FishingRodItem extends Item implements Vanishable {
  ...
  public InteractionResultHolder < ItemStack > use(Level level, Player player, InteractionHand interactionHand) {    ItemStack itemstack = player.getItemInHand(interactionHand);
    if (player.fishing != null) {
      if (!level.isClientSide) {
        int i = player.fishing.retrieve(itemstack);
        itemstack.hurtAndBreak(i, player, (p_41288_) - > {
          p_41288_.broadcastBreakEvent(interactionHand);
        });
      }      level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.FISHING_BOBBER_RETRIEVE, SoundSource.NEUTRAL, 1.0 F, 0.4 F / (level.getRandom().nextFloat() * 0.4 F + 0.8 F));
      player.gameEvent(GameEvent.ITEM_INTERACT_FINISH);
    } else {
      level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.NEUTRAL, 0.5 F, 0.4 F / (level.getRandom().nextFloat() * 0.4 F + 0.8 F));
      if (!level.isClientSide) {
        int k = EnchantmentHelper.getFishingSpeedBonus(itemstack);
        int j = EnchantmentHelper.getFishingLuckBonus(itemstack);
        level.addFreshEntity(new FishingHook(player, level, interactionHand.equals(InteractionHand.OFF_HAND) ? -1 : player.getInventory().selected, j, k));
      }      player.awardStat(Stats.ITEM_USED.get(this));
      player.gameEvent(GameEvent.ITEM_INTERACT_START);
    }    return InteractionResultHolder.sidedSuccess(itemstack, level.isClientSide());
  }
  ...
}
Our last problem to solve, is to uncast fishing rods if two separate rods are being swapped via offhand and mainhand. But, if we are transferring a fishing rod from the offhand to mainhand, it will update the selected slot of the fishing rod. This will also work vice versa.
net.minecraft.world.entity.LivingEntity.java // Updated
public abstract class LivingEntity extends Entity implements Attackable {
  ...
  private void handleHandSwap(Map < EquipmentSlot, ItemStack > p_21092_) {
    ItemStack itemstack = p_21092_.get(EquipmentSlot.MAINHAND);
    ItemStack itemstack1 = p_21092_.get(EquipmentSlot.OFFHAND);
    if (itemstack != null && itemstack1 != null && ItemStack.matches(itemstack, this.getLastHandItem(EquipmentSlot.OFFHAND)) && ItemStack.matches(itemstack1, this.getLastHandItem(EquipmentSlot.MAINHAND))) {
      ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundEntityEventPacket(this, (byte) 55));
      p_21092_.remove(EquipmentSlot.MAINHAND);
      p_21092_.remove(EquipmentSlot.OFFHAND);
      this.setLastHandItem(EquipmentSlot.MAINHAND, itemstack.copy());
      this.setLastHandItem(EquipmentSlot.OFFHAND, itemstack1.copy());
    }   Â
if (this instanceof Player player) {
      if (player.fishing != null) {
        if (itemstack1 != null && itemstack != null && itemstack.is(Items.FISHING_ROD) && itemstack1.is(Items.FISHING_ROD))
          player.fishing.discard();
        else
          player.fishing.setRodSlot(player.getInventory().selected);
      }
    }
  }
  ...
}
This code analysis is not an official solution to resolve the fishing rod swapping bug.
But it sure does give an idea on how it SHOULD be solved.
Thank you for tuning into my TED talk!
Confirmed, the switched Fishing rod takes damaged from the cast.