Kafka producer sends a message to a specific partition based on DefaultPartitioner, custom partitioner, or pass partition information while sending a message to get write to a specific partition.
The defined key as null or not null is based on your use cases and requirements but the key purpose is to distribute your messages on different partitions to get consumed by multiple consumers of the consumer group.
The nonnull key makes sure a similar key will park on the same partition which will help you to group multiple similar keys on the same bucket to further analysts at the same time null key make you distribute your messages evenly.
The nonnull key always helps to pass meta details of message for further processing. I would like to prefer to pass the nonnull key with a custom partitioner to control message flow. But it's up to specific requirement and if you want to pass key null that's absolutely fine.
Note: In future release Apache Kafka (2.5) you can able to define
RoundRobin partitioner as partition strategy(KIP-369) which not needed
to the key to be null.
https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=89070828
- If you are not defined custom partitioner it will use the default partitioner
Before Apache Kafka 2.4 it will be going through cycle one after another and send the record to each one.
In this case, the old partitioning strategy before Apache Kafka 2.4 would be to cycle through the topic’s partitions and send a record to each one.
But as you understand messages go as a batch with configuration parameter linger.ms it may performance impact on small batches as each one goes to specific partitions so Apache Kafka introducer new Sticky partitioner in case of the null key
Apache Kafka introduced Sticky Partitioner(KIP-480) in Apache Kafka 2.4 in case of key null in the default partitioner as mentioned below
Sticky partitioning strategy
The sticky partitioner addresses the problem of spreading out records without keys into smaller batches by picking a single partition to send all non-keyed records. Once the batch at that partition is filled or otherwise completed, the sticky partitioner randomly chooses and “sticks ” to a new partition. That way, over a larger period of time, records are about evenly distributed among all the partitions while getting the added benefit of larger batch sizes.

Please click here for more detail
If you passing a nonnull key and not defined custom partitioner It will be used DefaultPartitioner to identify partition to publish messages.
DefaultPartitioner makes use of MurmurHash, a non-cryptographic hash function which is usually used for hash-based lookup. This hash is then used in a modulo operation (% numPartitions) in order to ensure that the returned partition is within the range [0, N] where N is the number of partitions of the topic.
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
You can also define Custom Partitioner and implement logic to select the partition
https://kafka.apache.org/10/javadoc/org/apache/kafka/clients/producer/Partitioner.html
Pass partition explicitly while publishing a message
/** * Creates a record to be sent to a specified topic and partition */
public ProducerRecord(String topic, Integer partition, K key, V value)
{
this(topic, partition, null, key, value, null);
}