problem
Creating an instance from a backup (createVMFromBackup with startvm=true) fails with a NullPointerException in the API response, even though the instance is created correctly, its volumes are fully restored from the backup and the VM boots and runs.
The user-visible effect is a red error in the UI with no indication that the operation actually succeeded, and the generated password is not returned.
2026-08-05 18:37:06,819 ERROR [c.c.a.ApiAsyncJobDispatcher] (API-Job-Executor-20:[ctx-f743d928, job-138]) (logid:5f51fca6) Unexpected exception while executing org.apache.cloudstack.api.command.admin.vm.CreateVMFromBackupCmdByAdmin java.lang.NullPointerException: Cannot invoke "java.util.Map.get(Object)" because the return value of "com.cloud.utils.Pair.second()" is null
at com.cloud.vm.UserVmManagerImpl.startVirtualMachine(UserVmManagerImpl.java:5342)
at com.cloud.vm.UserVmManagerImpl.restoreVMFromBackup(UserVmManagerImpl.java:9848)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at jdk.proxy3/jdk.proxy3.$Proxy202.restoreVMFromBackup(Unknown Source)
at org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd.execute(CreateVMFromBackupCmd.java:124)
at com.cloud.api.ApiDispatcher.dispatch(ApiDispatcher.java:173)
at com.cloud.api.ApiAsyncJobDispatcher.runJob(ApiAsyncJobDispatcher.java:110)
at org.apache.cloudstack.framework.jobs.impl.AsyncJobManagerImpl$5.runInContext(AsyncJobManagerImpl.java:698)
...
2026-08-05 18:37:06,820 DEBUG [o.a.c.f.j.i.AsyncJobManagerImpl] (API-Job-Executor-20:[ctx-f743d928, job-138]) (logid:5f51fca6) Complete async job-138, jobStatus: FAILED, resultCode: 530, result: {"uuidList":[],"errorcode":"530","errortext":"Cannot invoke \"java.util.Map.get(Object)\" because the return value of \"com.cloud.utils.Pair.second()\" is null"}
Root cause
The trigger is a source VM whose template has enable_password = 1 ("Password Enabled").
restoreVMFromBackup starts the VM twice:
- First with
Param.ReturnAfterVolumePrepare = true - UserVmManagerImpl.java#L9808. On this run vm.isUpdateParameters() is still true, so the params map is built (L5795) and the flag is then cleared - vm.setUpdateParameters(false) at L5843.
- Then, when
cmd.getStartVm() is true, it performs the real start - UserVmManagerImpl.java#L9848.
On the second run vm.isUpdateParameters() is already false, so params stays null and the returned Pair.second() is null. The private startVirtualMachine overload then dereferences it unconditionally whenever the template has a password enabled - UserVmManagerImpl.java#L5339-L5342:
VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId());
if (template.isEnablePassword()) {
// this value is not being sent to the backend; need only for api display purposes
vm.setPassword((String)vmParamPair.second().get(VirtualMachineProfile.Param.VmPassword));
}
The exception is thrown outside the try block at L9806-L9829, so the expunge() cleanup in the catch does not run. That is why the VM survives in a correct, running state and only the API response fails - the VM.CREATE.FROM.BACKUP event itself is recorded as Completed.
The same unguarded dereference is present on main - UserVmManagerImpl.java#L5597.
versions
- CloudStack 4.22.1.0 (packages
cloudstack-management-4.22.1.0-1, cloudstack-agent-4.22.1.0-1); code inspected on tag 4.22.1.0 and on main
- Hypervisor: KVM on AlmaLinux 9.8,
qemu-kvm 10.1.0, libvirt 11.10.0, OpenJDK 17.0.20
- Primary storage: Ceph RBD (Ceph 20.2.2)
- Backup provider: NAS Backup & Recovery (
backup.framework.provider.plugin = nas), repository on an NFS export backed by CephFS
- Database: MySQL 8.0.46
- 3 management servers, single advanced zone
The steps to reproduce the bug
- Register a template with Password Enabled = true (
enable_password = 1).
- Deploy an instance from that template.
- Enable the backup framework with the
nas provider, assign a backup offering to the instance and take a backup.
- Go to Backups, select the backup, choose Create Instance from Backup and leave Start Instance = Yes.
- The job fails with the NPE above (
errorcode 530), but listVirtualMachines shows the new instance in state Running with its volumes correctly restored.
The bug does not appear when the template has Password Enabled unset, or when Start Instance = No is selected.
What to do about it?
Workaround for users: create the instance with Start Instance = No and start it afterwards - the manual start path (StartVMCmd) only uses Pair.first(), so it is unaffected.
Minimal fix - guard the dereference at UserVmManagerImpl.java:5342:
if (template.isEnablePassword() && vmParamPair.second() != null) {
vm.setPassword((String)vmParamPair.second().get(VirtualMachineProfile.Param.VmPassword));
}
A cleaner fix would be for restoreVMFromBackup to reuse the params map returned by the first startVirtualMachine call instead of letting the second call re-derive it from a flag the first call has already consumed - that would also make the generated password available in the API response, which is arguably the intended behaviour.
problem
Creating an instance from a backup (
createVMFromBackupwithstartvm=true) fails with aNullPointerExceptionin the API response, even though the instance is created correctly, its volumes are fully restored from the backup and the VM boots and runs.The user-visible effect is a red error in the UI with no indication that the operation actually succeeded, and the generated password is not returned.
Root cause
The trigger is a source VM whose template has
enable_password = 1("Password Enabled").restoreVMFromBackupstarts the VM twice:Param.ReturnAfterVolumePrepare = true- UserVmManagerImpl.java#L9808. On this runvm.isUpdateParameters()is stilltrue, so the params map is built (L5795) and the flag is then cleared -vm.setUpdateParameters(false)at L5843.cmd.getStartVm()is true, it performs the real start - UserVmManagerImpl.java#L9848.On the second run
vm.isUpdateParameters()is alreadyfalse, soparamsstaysnulland the returnedPair.second()isnull. The privatestartVirtualMachineoverload then dereferences it unconditionally whenever the template has a password enabled - UserVmManagerImpl.java#L5339-L5342:The exception is thrown outside the
tryblock at L9806-L9829, so theexpunge()cleanup in the catch does not run. That is why the VM survives in a correct, running state and only the API response fails - theVM.CREATE.FROM.BACKUPevent itself is recorded asCompleted.The same unguarded dereference is present on
main- UserVmManagerImpl.java#L5597.versions
cloudstack-management-4.22.1.0-1,cloudstack-agent-4.22.1.0-1); code inspected on tag4.22.1.0and onmainqemu-kvm 10.1.0,libvirt 11.10.0, OpenJDK 17.0.20backup.framework.provider.plugin = nas), repository on an NFS export backed by CephFSThe steps to reproduce the bug
enable_password = 1).nasprovider, assign a backup offering to the instance and take a backup.errorcode 530), butlistVirtualMachinesshows the new instance in stateRunningwith its volumes correctly restored.The bug does not appear when the template has Password Enabled unset, or when Start Instance = No is selected.
What to do about it?
Workaround for users: create the instance with Start Instance = No and start it afterwards - the manual start path (
StartVMCmd) only usesPair.first(), so it is unaffected.Minimal fix - guard the dereference at
UserVmManagerImpl.java:5342:A cleaner fix would be for
restoreVMFromBackupto reuse the params map returned by the firststartVirtualMachinecall instead of letting the second call re-derive it from a flag the first call has already consumed - that would also make the generated password available in the API response, which is arguably the intended behaviour.