Allow me to expand on @Tymski's nice answer.
Let's start with a review of hardware interrupts.
These are can occur at any time (assuming the are enabled) and are thus asynchronous to the current execution stream. The CPU accepts hardware interrupts by listening to external lines in parallel with instruction stream execution. When an interrupt request is detected on these external lines the processor allows execution to continue to advance until a good breaking point. For example, a pipelined processor will allow the instructions already started to finish, though not starting any new instructions. When it is ready, the CPU will start interrupt processing. This can be a relatively complex process, also sometimes, or some parts thereof, referred to as a context switch. User mode execution ceases. Privileged mode is entered (i.e. the OS). The current context is saved -- CPU registers holding user mode values, mostly; the location to save is usually somewhere in the privileged context. The processor then gives flow of control to a low level interrupt handler. The privileged context interrupt handler then determines the next stream to execute, such as an appropriate somewhat higher level interrupt handler.
A software interrupt is very similar in mechanism, with the main difference being that it occurs by the execution of a software interrupt instruction, sometimes called a trap. So, these occur synchronously to the currently executing instruction stream. The same general context switch from user mode to privileged mode is performed borrowing the same hardware, which is one reason it is called an interrupt. These traps are typically used for user mode code to accomplish system calls.
A software exception can refer to the same thing, except rather than being triggered by software interrupt instruction, it is triggered by abnormal condition detected by the CPU in the current instruction stream execution, such as null pointer dereference or integer divide by zero.
An exception, the term used alone, usually refers so a programming language mechanism for detecting and handling synchronous errors in the current thread. These can involve software exception (e.g. be triggered by the CPU for certain instructions), or can be detected by explicit test inserted into the execution code by a compiler or JIT, or by user or library software that explicitly throws.
As @Tymski says, these terms are often interchanged, sometimes erroneously, but also sometimes due to context.