20

I have a REST service which sends me a large ISO file ,there are no issues in the REST service . Now I have written a Web application which calls the rest service to get the file ,on the client(web app) side I receive a Out Of memory Exception.Below is my code

HttpHeaders headers = new HttpHeaders();//1 Line headers.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM));//2 Line headers.set("Content-Type","application/json");//3 Line headers.set("Cookie", "session=abc");//4 Line HttpEntity statusEntity=new HttpEntity(headers);//5 Line String uri_status=new String("http://"+ip+":8080/pcap/file?fileName={name}");//6 Line ResponseEntity<byte[]>resp_status=rt.exchange(uri_status, HttpMethod.GET, statusEntity, byte[].class,"File5.iso");//7 Line 

I receive out of memory exception at 7 line ,I guess i will have to buffer and get in parts ,but dont know how can i get this file from the server ,the size of the file is around 500 to 700 MB . Can anyone please assist .

Exception Stack:

 org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.OutOfMemoryError: Java heap space org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:972) org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882) org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778) javax.servlet.http.HttpServlet.service(HttpServlet.java:622) javax.servlet.http.HttpServlet.service(HttpServlet.java:729) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) root cause java.lang.OutOfMemoryError: Java heap space java.util.Arrays.copyOf(Arrays.java:3236) java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118) java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93) java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153) org.springframework.util.FileCopyUtils.copy(FileCopyUtils.java:113) org.springframework.util.FileCopyUtils.copyToByteArray(FileCopyUtils.java:164) org.springframework.http.converter.ByteArrayHttpMessageConverter.readInternal(ByteArrayHttpMessageConverter.java:58) org.springframework.http.converter.ByteArrayHttpMessageConverter.readInternal(ByteArrayHttpMessageConverter.java:1) org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:153) org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:81) org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:627) org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1) org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:454) org.springframework.web.client.RestTemplate.execute(RestTemplate.java:409) org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:385) com.pcap.webapp.HomeController.getPcapFile(HomeController.java:186) 

My Server Side REST Service Code which is working fine is

@RequestMapping(value = URIConstansts.GET_FILE, produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE}, method = RequestMethod.GET) public void getFile(@RequestParam(value="fileName", required=false) String fileName,HttpServletRequest request,HttpServletResponse response) throws IOException{ byte[] reportBytes = null; File result=new File("/home/arpit/Documents/PCAP/dummyPath/"+fileName); if(result.exists()){ InputStream inputStream = new FileInputStream("/home/arpit/Documents/PCAP/dummyPath/"+fileName); String type=result.toURL().openConnection().guessContentTypeFromName(fileName); response.setHeader("Content-Disposition", "attachment; filename=" + fileName); response.setHeader("Content-Type",type); reportBytes=new byte[100];//New change OutputStream os=response.getOutputStream();//New change int read=0; while((read=inputStream.read(reportBytes))!=-1){ os.write(reportBytes,0,read); } os.flush(); os.close(); } 
7
  • Could you post the exception (stack trace), please? Commented Oct 7, 2015 at 9:21
  • you are trying to read the whole file into memory in line ResponseEntity<byte[]>resp_status... You need to use buffers on both ends, take a look at stackoverflow.com/questions/15800565/… Commented Oct 7, 2015 at 9:37
  • @freakman yes I followed the same post for the server side ,i got out of memory in the REST code and followed the same post and it got solved.But on the client side I am facing issue Commented Oct 7, 2015 at 9:41
  • yes, you did that on server side, but client is still reading whole file and trying to put that in byte[]. You can take url, and write this stream directly to a file - take a look at - stackoverflow.com/questions/22244985/… Commented Oct 7, 2015 at 11:32
  • How much ram you have on server-side? You can also change the connectionTimeout in tomcat, so this problem doesnt happen Commented Oct 7, 2015 at 12:38

5 Answers 5

30

Here is how I do it. Based on hints from this Spring Jira issue.

RestTemplate restTemplate // = ...; // Optional Accept header RequestCallback requestCallback = request -> request.getHeaders() .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL)); // Streams the response instead of loading it all in memory ResponseExtractor<Void> responseExtractor = response -> { // Here I write the response to a file but do what you like Path path = Paths.get("some/path"); Files.copy(response.getBody(), path); return null; }; restTemplate.execute(URI.create("www.something.com"), HttpMethod.GET, requestCallback, responseExtractor); 

From the aforementioned Jira issue:

Note that you cannot simply return the InputStream from the extractor, because by the time the execute method returns, the underlying connection and stream are already closed.

Update for Spring 5

Spring 5 introduced the WebClient class which allows asynchronous (e.g. non-blocking) http requests. From the doc:

By comparison to the RestTemplate, the WebClient is:

  • non-blocking, reactive, and supports higher concurrency with less hardware resources.
  • provides a functional API that takes advantage of Java 8 lambdas.
  • supports both synchronous and asynchronous scenarios.
  • supports streaming up or down from a server.

To get WebClient in Spring Boot, you need this dependency:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> 

For the moment, I'm sticking with RestTemplate because I don't want to pull in another dependency only to get access to WebClient.

Sign up to request clarification or add additional context in comments.

6 Comments

Why is the inputstream closed? Is there a way to return a stream instead of a Path?
@danip see my update. By the way, if you have an interest in this, please take the time to approve my documentation topic !
I was more curios on why than how! I think the answer is that the RestTemplate needs to make sure the InputStream is closed. The responseExtractor even throws an IOException so it's really suggesting to try and store the file locally. After that you can return the File or Path.
I ended up implementing a custom InputStreamWrapper which takes the response InputStream and internally uses a temporary file to cache the file and deletes it after closing the stream. Not really elegant but i didn't found a better solution yet..
Can you give an example of this code for Spring 5 WebClient?
|
8

As @bernie mentioned you can use WebClient to achieve this:

public Flux<DataBuffer> downloadFileUrl( ) throws IOException { WebClient webClient = WebClient.create(); // Request service to get file data return Flux<DataBuffer> fileDataStream = webClient.get() .uri( this.fileUrl ) .accept( MediaType.APPLICATION_OCTET_STREAM ) .retrieve() .bodyToFlux( DataBuffer.class ); } @GetMapping( produces = MediaType.APPLICATION_OCTET_STREAM_VALUE ) public void downloadFile( HttpServletResponse response ) throws IOException { Flux<DataBuffer> dataStream = this.downloadFileUrl( ); // Streams the stream from response instead of loading it all in memory DataBufferUtils.write( dataStream, response.getOutputStream() ) .map( DataBufferUtils::release ) .blockLast(); } 

You can still use WebClient even if you don't have Reactive Server stack - Rossen Stoyanchev (a member of Spring Framework team) explains it quite well in the Guide to "Reactive" for Spring MVC Developers presentation. During this presentation, Rossen Stoyanchev mentioned that they thought about deprecating RestTemplate, but they have decided to postpone it after all, but it may still happen in the future!

The main disadvantage of using WebClient so far it's a quite steep learning curve (reactive programming), but I think there is no way to avoid in the future, so better to take a look on it sooner than latter.

2 Comments

When I use the code in Controller, I get Prematurely closed exception, can I get a link to working sample of the same
@abitcode I've updated above example with the working controller method (copied and pasted from my private project). I hope it will help you resolve your problem
0

This prevents loading the entire request into memory.

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setBufferRequestBody(false); RestTemplate rest = new RestTemplate(requestFactory); 

For java.lang.OutOfMemoryError: Java heap space can be solved adding more memory to the JVM:

-Xmxn Specifies the maximum size, in bytes, of the memory allocation pool. This value must a multiple of 1024 greater than 2 MB. Append the letter k or K to indicate kilobytes, or m or M to indicate megabytes. The default value is chosen at runtime based on system configuration.

For server deployments, -Xms and -Xmx are often set to the same value. See Garbage Collector Ergonomics at http://docs.oracle.com/javase/7/docs/technotes/guides/vm/gc-ergonomics.html

Examples:

-Xmx83886080
-Xmx81920k
-Xmx80m

Probably the problem you have is not strictly related to the request you are trying to execute (download large file) but the memory allocated for the process is not enough.

6 Comments

I updated the code ,but receive the same String uri_status=new String("http://"+ip+":8080/pcap/file?fileName={name}"); SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setBufferRequestBody(false); rt.setRequestFactory(requestFactory); ResponseEntity<byte[]>resp_status=rt.exchange(uri_status, HttpMethod.GET, statusEntity, byte[].class,"File5.iso");
Here is my STS JVM Config -startup ../Eclipse/plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar --launcher.library ../Eclipse/plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.1.300.v20150602-1417 -product org.springsource.sts.ide --launcher.defaultAction openFile -vmargs -Dosgi.requiredJavaVersion=1.6 -Xms40m -Xmx768m -XX:MaxPermSize=256m -Xverify:none -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts -Xdock:icon=../Resources/sts.icns
Try to increase Xmx from -Xmx768m to -Xmx1024
"java.lang.OutOfMemoryError: Java heap space can be solved adding more memory to the JVM" are you kidding me? This isn't a solution - you just add more memory, but then when a larger file comes what will happen?
Just to clarify for anyone who suffers the same issue: This only applies for sending files to a rest service, not for downloading them!
|
0

A better version of above correct answer could be the below code. This method will send download request to another application or service acting as actual source of truth for downloaded information.

public void download(HttpServletRequest req, HttpServletResponse res, String url) throws ResourceAccessException, GenericException { try { logger.info("url::" + url); if (restTemplate == null) logger.info("******* rest template is null***********************"); RequestCallback requestCallback = request -> request.getHeaders() .setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL)); // Streams the response instead of loading it all in memory ResponseExtractor<ResponseEntity<InputStream>> responseExtractor = response -> { String contentDisposition = response.getHeaders().getFirst("Content-Disposition"); if (contentDisposition != null) { // Temporary location for files that will be downloaded from micro service and // act as final source of download to user String filePath = "/home/devuser/fileupload/download_temp/" + contentDisposition.split("=")[1]; Path path = Paths.get(filePath); Files.copy(response.getBody(), path, StandardCopyOption.REPLACE_EXISTING); // Create a new input stream from temporary location and use it for downloading InputStream inputStream = new FileInputStream(filePath); String type = req.getServletContext().getMimeType(filePath); res.setHeader("Content-Disposition", "attachment; filename=" + contentDisposition.split("=")[1]); res.setHeader("Content-Type", type); byte[] outputBytes = new byte[100]; OutputStream os = res.getOutputStream(); int read = 0; while ((read = inputStream.read(outputBytes)) != -1) { os.write(outputBytes, 0, read); } os.flush(); os.close(); inputStream.close(); } return null; }; restTemplate.execute(url, HttpMethod.GET, requestCallback, responseExtractor); } catch (Exception e) { logger.info(e.toString()); throw e; } } 

Comments

-1

You should use multipart file attachment, so the file stream isn't load into memory. In this example, I use a rest service implemented with Apache CXF.

... import org.apache.cxf.jaxrs.ext.multipart.Attachment; ... @Override @Path("/put") @Consumes("multipart/form-data") @Produces({ "application/json" }) @POST public SyncResponseDTO put( List<Attachment> attachments) { SyncResponseDTO response = new SyncResponseDTO(); try { for (Attachment attr : attachments) { log.debug("get input filestream: " + new Date()); InputStream is = attr.getDataHandler().getInputStream(); 

5 Comments

But I have my REST service in Spring MVC ,hence I have to use Rest Template
You can use Multipart File Upload in Rest Template too.
Yes But this is download ,i want to get the File from the server (a rest which is written in spring mvc)
Sorry, I misunderstood your question, please try this: stackoverflow.com/a/7107001/1897196
stackoverflow.com/a/7107001/1897196 is related to the file upload ie:server side in which the response sends the file,my issue is with getting the file from the server

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.