Ideally, this will be done without first decrypting the file, and then re-encrypting it with the new key.
This part, at least, is easy. Each item, when uploaded, gets a symmetric (e.g. AES) "item encryption key" (IEK) that is unique, totally random, and never stored in plain text anywhere at all (except in RAM and CPU registers while encrypting or decrypting). The item is encrypted with that IEK, and then the IEK is itself encrypted (or "wrapped") with the rotating key that is common to multiple items. The wrapped IEK is stored persistently, along with the encrypted item. This way, when you need to rotate keys, you just decrypt the IEKs with the old rotating key, and re-encrypt them with the new one, and overwrite the old wrapped IEKs in storage. This approach is widely used for encrypting data at rest (e.g. encrypted files, storage volumes, databases, data within databases, password vaults, email and chat message histories, etc.).
However, as the comments under your question point out, there is no technical solution in the world for expiring a key such that it can't be used anymore. A key is just a series of bits, or a really big number if you prefer; cryptography is just doing complicated math with it. Similarly, JWTs and X.509 certs don't actually expire in any technical sense; they have a "not valid after" field, but it's up to whatever is consuming them (the validating server, or the TCP client, or whatever) to know the current date+time correctly, to check it against the JWT/cert, and to decide what to do with the result. If the validating party has the wrong time info, or fails to do that check correctly, or wants to ignore the result, it can still accept the JWT or cert. As a practical matter the validating party generally won't do that, because it's trying to protect itself; it's not the attacker, and the attacker does not get to decide. But that isn't always the case. For example, it's entirely possible to use the public key from an expired cert to verify a signature that was created using the corresponding private key. It doesn't even matter whether the cert was already expired when the private key was used, or not. Similarly, it's entirely possible for an attacker to use a key you've already rotated out to decrypt data.
You can, of course, put policy barriers in place. They can even be implemented in software (this is, after all, what's going on when a browser refuses to accept a server's expired TLS cert, or a server refuses to accept a client's expired JWT). But the bits themselves are still there, and can still be used for the complicated math.