Could a controller return binary data?
With the BinaryFileResponse class, you can return the content of a file in the response. The FileDownloadController class is an example of controller returning the content of a file. It's the controller for /system/files/{scheme}, /system/files/{filepath}, and /system/temporary.
See its FileDownloadController::download() method.

public function download(Request $request, $scheme = 'private') {
  $target = $request-&gt;query
    -&gt;get('file');

  // Merge remaining path arguments into relative file path.
  $uri = $scheme . '://' . $target;
  if (file_stream_wrapper_valid_scheme($scheme) &amp;&amp; file_exists($uri)) {

    // Let other modules provide headers and controls access to the file.
    $headers = $this
      -&gt;moduleHandler()
      -&gt;invokeAll('file_download', [
      $uri,
    ]);
    foreach ($headers as $result) {
      if ($result == -1) {
        throw new AccessDeniedHttpException();
      }
    }
    if (count($headers)) {

      // \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond()
      // sets response as not cacheable if the Cache-Control header is not
      // already modified. We pass in FALSE for non-private schemes for the
      // $public parameter to make sure we don't change the headers.
      return new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private');
    }
    throw new AccessDeniedHttpException();
  }
  throw new NotFoundHttpException();
}


The ExportForm class is another example of how to return a file, even if it's a form class. What changes is how the response is used, but how the BinaryFileResponse class is used is still relevant for a controller.
The relevant code is the following one, in ExportForm::submitForm().

$response = new BinaryFileResponse($uri);
$response-&gt;setContentDisposition('attachment', $filename);