pola-rs/polars · error · ValueError

cannot use 'on' in conjunction with 'left_on' or 'right_on'

Error message

cannot use 'on' in conjunction with 'left_on' or 'right_on'

What it means

LazyFrame.join() does not allow `on` together with `left_on`/`right_on` — they are two alternative ways to specify join keys, and using both is ambiguous. The library raises ValueError early to prevent silently inconsistent key specifications.

Solutions

  1. Remove `on` and keep only left_on/right_on when key names differ between frames
  2. Remove left_on/right_on and keep only `on` when the key column has the same name in both frames

Example fix

// before
df.join(other, on='id', left_on='left_id', right_on='right_id')
// after
df.join(other, left_on='left_id', right_on='right_id')
Defensive patterns

Strategy: validation

Validate before calling

def check_join_args(on, left_on, right_on):
    if on is not None and (left_on is not None or right_on is not None):
        raise ValueError('use on= OR left_on/right_on, not both')

Type guard

def keys_are_exclusive(on, left_on, right_on) -> bool:
    return not (on is not None and (left_on is not None or right_on is not None))

Try / catch

try:
    out = df.join(other, on=key)
except ValueError as e:
    if "in conjunction with 'left_on'" in str(e):
        out = df.join(other, left_on=lk, right_on=rk)
    else:
        raise

Prevention

When it happens

Trigger: Calling lf.join(other, on='id', left_on='lkey') or lf.join(other, on='id', right_on='rkey'); any call where on is not None AND at least one of left_on/right_on is not None.

Common situations: Refactoring a join from same-name keys to different-name keys and leaving the old `on=` in place; IDE autocomplete filling both parameters; copy-paste between join variants.

Related errors


AI-assisted analysis of pola-rs/polars@fe841f959e (2026-09-18). Data as JSON: /api/errors/ff15c0c5cd77a7de. Report an issue: GitHub.

Appendix: source

Thrown at py-polars/src/polars/lazyframe/frame.py:6226

        │ 2   ┆ 7.0 ┆ b   ┆ y     ┆ b         │
        │ 2   ┆ 7.0 ┆ b   ┆ z     ┆ d         │
        │ 3   ┆ 8.0 ┆ c   ┆ x     ┆ a         │
        │ 3   ┆ 8.0 ┆ c   ┆ y     ┆ b         │
        │ 3   ┆ 8.0 ┆ c   ┆ z     ┆ d         │
        └─────┴─────┴─────┴───────┴───────────┘
        """
        require_same_type(self, other)

        if maintain_order is None:
            maintain_order = "none"

        uses_on = on is not None
        uses_left_on = left_on is not None
        uses_right_on = right_on is not None
        uses_lr_on = uses_left_on or uses_right_on
        if uses_on and uses_lr_on:
            msg = "cannot use 'on' in conjunction with 'left_on' or 'right_on'"
            raise ValueError(msg)
        elif uses_left_on != uses_right_on:
            msg = "'left_on' requires corresponding 'right_on'"
            raise ValueError(msg)

        if how == "outer":  # type: ignore[comparison-overlap]
            msg = "use of `how='outer'` should be replaced with `how='full'`."
            raise ValueError(msg)
        elif how == "outer_coalesce":  # type: ignore[comparison-overlap]
            msg = "use of `how='outer_coalesce'` should be replaced with `how='full', coalesce=True`."
            raise ValueError(msg)
        elif how == "cross":
            if uses_on or uses_lr_on:
                msg = "cross join should not pass join keys"
                raise ValueError(msg)
            return self._from_pyldf(
                self._ldf.join(
                    other._ldf,
                    [],

View on GitHub (pinned to fe841f959e)